diff --git a/app/build.gradle b/app/build.gradle
index 06015ea874..73a4e24d03 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -158,8 +158,8 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.2'
}
-def canonicalVersionCode = 162
-def canonicalVersionName = "1.10.3"
+def canonicalVersionCode = 163
+def canonicalVersionName = "1.10.4"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2d8473b849..972ec30be1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -254,6 +254,10 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.loki.activities.HomeActivity" />
+
{
- String encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this);
- byte[] profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey);
+ ThreadUtils.queue(() -> {
+ // Don't generate a new profile key here; we do that when the user changes their profile picture
+ String encodedProfileKey = TextSecurePreferences.getProfileKey(ApplicationContext.this);
try {
- File profilePicture = AvatarHelper.getAvatarFile(this, Address.fromSerialized(userPublicKey));
- StreamDetails stream = new StreamDetails(new FileInputStream(profilePicture), "image/jpeg", profilePicture.length());
- FileServerAPI.shared.uploadProfilePicture(FileServerAPI.shared.getServer(), profileKey, stream, () -> {
- TextSecurePreferences.setLastProfilePictureUpload(this, new Date().getTime());
- TextSecurePreferences.setProfileAvatarId(this, new SecureRandom().nextInt());
- ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey);
+ // Read the file into a byte array
+ InputStream inputStream = AvatarHelper.getInputStreamFor(ApplicationContext.this, Address.fromSerialized(userPublicKey));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int count;
+ byte[] buffer = new byte[1024];
+ while ((count = inputStream.read(buffer, 0, buffer.length)) != -1) {
+ baos.write(buffer, 0, count);
+ }
+ baos.flush();
+ byte[] profilePicture = baos.toByteArray();
+ // Re-upload it
+ ProfilePictureUtilities.INSTANCE.upload(profilePicture, encodedProfileKey, ApplicationContext.this).success(unit -> {
+ // Update the last profile picture upload date
+ TextSecurePreferences.setLastProfilePictureUpload(ApplicationContext.this, new Date().getTime());
return Unit.INSTANCE;
});
} catch (Exception exception) {
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 40981b5cc7..1bc9e43de0 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt
@@ -108,28 +108,28 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
return null // TODO: Implement
}
- override fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) {
+ override fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) {
val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return
val attachmentPointer = SignalServiceAttachmentPointer(uploadResult.id,
- attachmentStream.contentType,
- attachmentKey,
- Optional.of(Util.toIntExact(attachmentStream.length)),
- attachmentStream.preview,
- attachmentStream.width, attachmentStream.height,
- Optional.fromNullable(uploadResult.digest),
- attachmentStream.fileName,
- attachmentStream.voiceNote,
- attachmentStream.caption,
- uploadResult.url);
+ attachmentStream.contentType,
+ attachmentKey,
+ Optional.of(Util.toIntExact(attachmentStream.length)),
+ attachmentStream.preview,
+ attachmentStream.width, attachmentStream.height,
+ Optional.fromNullable(uploadResult.digest),
+ attachmentStream.fileName,
+ attachmentStream.voiceNote,
+ attachmentStream.caption,
+ uploadResult.url);
val attachment = PointerAttachment.forPointer(Optional.of(attachmentPointer), databaseAttachment.fastPreflightId).get()
database.updateAttachmentAfterUploadSucceeded(databaseAttachment.attachmentId, attachment)
}
- override fun updateAttachmentAfterUploadFailed(attachmentId: Long) {
+ override fun handleFailedAttachmentUpload(attachmentId: Long) {
val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return
- database.updateAttachmentAfterUploadFailed(databaseAttachment.attachmentId)
+ database.handleFailedAttachmentUpload(databaseAttachment.attachmentId)
}
override fun getMessageID(serverID: Long): Long? {
@@ -230,23 +230,24 @@ fun DatabaseAttachment.toAttachmentStream(context: Context): SessionServiceAttac
fun DatabaseAttachment.toSignalAttachmentPointer(): SignalServiceAttachmentPointer? {
if (TextUtils.isEmpty(location)) { return null }
- if (TextUtils.isEmpty(key)) { return null }
-
+ // `key` can be empty in an open group context (no encryption means no encryption key)
return try {
- val id: Long = location!!.toLong()
- val key: ByteArray = Base64.decode(key!!)
- SignalServiceAttachmentPointer(id,
- contentType,
- key,
- Optional.of(Util.toIntExact(size)),
- Optional.absent(),
- width,
- height,
- Optional.fromNullable(digest),
- Optional.fromNullable(fileName),
- isVoiceNote,
- Optional.fromNullable(caption),
- url)
+ val id = location!!.toLong()
+ val key = Base64.decode(key!!)
+ SignalServiceAttachmentPointer(
+ id,
+ contentType,
+ key,
+ Optional.of(Util.toIntExact(size)),
+ Optional.absent(),
+ width,
+ height,
+ Optional.fromNullable(digest),
+ Optional.fromNullable(fileName),
+ isVoiceNote,
+ Optional.fromNullable(caption),
+ url
+ )
} catch (e: Exception) {
null
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
index 92845a3ab0..59fc091455 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
@@ -384,6 +384,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
PublicChatInfoUpdateWorker.scheduleInstant(this, publicChat.getServer(), publicChat.getChannel());
} else 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);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
index 53ab9de4ed..dae7408bb9 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
@@ -546,7 +546,7 @@ public class ConversationFragment extends Fragment
.deleteMessages(serverIDs, publicChat.getChannel(), publicChat.getServer(), isSentByUser)
.success(l -> {
for (MessageRecord messageRecord : messageRecords) {
- Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id, !messageRecord.isMms());
+ Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id, !messageRecord.isMms());
if (l.contains(serverID)) {
if (messageRecord.isMms()) {
DatabaseFactory.getMmsDatabase(getActivity()).delete(messageRecord.getId());
@@ -569,7 +569,7 @@ public class ConversationFragment extends Fragment
.deleteMessage(serverId, openGroupChat.getRoom(), openGroupChat.getServer())
.success(l -> {
for (MessageRecord messageRecord : messageRecords) {
- Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id, !messageRecord.isMms());
+ 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;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java
index b69f879993..59b7f52c7d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java
@@ -393,7 +393,7 @@ public class AttachmentDatabase extends Database {
database.update(TABLE_NAME, values, PART_ID_WHERE, id.toStrings());
}
- public void updateAttachmentAfterUploadFailed(@NonNull AttachmentId id) {
+ public void handleFailedAttachmentUpload(@NonNull AttachmentId id) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
index c9669ae1de..dc30c69bad 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
@@ -351,7 +351,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(group, server)
}
- override fun isMessageDuplicated(timestamp: Long, sender: String): Boolean {
+ override fun isDuplicateMessage(timestamp: Long, sender: String): Boolean {
return getReceivedMessageTimestamps().contains(timestamp)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java
index a9c2025258..26e3280f98 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java
@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.jobs;
-
import android.app.Application;
import android.text.TextUtils;
@@ -39,7 +38,7 @@ public class RetrieveProfileAvatarJob extends BaseJob implements InjectableType
private static final String TAG = RetrieveProfileAvatarJob.class.getSimpleName();
- private static final int MAX_PROFILE_SIZE_BYTES = 20 * 1024 * 1024;
+ private static final int MAX_PROFILE_SIZE_BYTES = 10 * 1024 * 1024;
private static final String KEY_PROFILE_AVATAR = "profile_avatar";
private static final String KEY_ADDRESS = "address";
@@ -51,18 +50,17 @@ public class RetrieveProfileAvatarJob extends BaseJob implements InjectableType
public RetrieveProfileAvatarJob(Recipient recipient, String profileAvatar) {
this(new Job.Parameters.Builder()
- .setQueue("RetrieveProfileAvatarJob" + recipient.getAddress().serialize())
- .addConstraint(NetworkConstraint.KEY)
- .setLifespan(TimeUnit.HOURS.toMillis(1))
- .setMaxAttempts(3)
- .build(),
+ .setQueue("RetrieveProfileAvatarJob" + recipient.getAddress().serialize())
+ .addConstraint(NetworkConstraint.KEY)
+ .setLifespan(TimeUnit.HOURS.toMillis(1))
+ .setMaxAttempts(10)
+ .build(),
recipient,
profileAvatar);
}
private RetrieveProfileAvatarJob(@NonNull Job.Parameters parameters, @NonNull Recipient recipient, String profileAvatar) {
super(parameters);
-
this.recipient = recipient;
this.profileAvatar = profileAvatar;
}
@@ -70,9 +68,10 @@ public class RetrieveProfileAvatarJob extends BaseJob implements InjectableType
@Override
public @NonNull
Data serialize() {
- return new Data.Builder().putString(KEY_PROFILE_AVATAR, profileAvatar)
- .putString(KEY_ADDRESS, recipient.getAddress().serialize())
- .build();
+ return new Data.Builder()
+ .putString(KEY_PROFILE_AVATAR, profileAvatar)
+ .putString(KEY_ADDRESS, recipient.getAddress().serialize())
+ .build();
}
@Override
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 3bc33c9746..9382bf83e6 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
@@ -24,7 +24,6 @@ import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import network.loki.messenger.R
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
@@ -315,23 +314,24 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
val threadID = thread.threadId
val recipient = thread.recipient
val threadDB = DatabaseFactory.getThreadDatabase(this)
- val dialogMessage: String
+ val message: String
if (recipient.isGroupRecipient) {
val group = DatabaseFactory.getGroupDatabase(this).getGroup(recipient.address.toString()).orNull()
if (group != null && group.admins.map { it.toString() }.contains(TextSecurePreferences.getLocalNumber(this))) {
- dialogMessage = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."
+ message = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."
} else {
- dialogMessage = resources.getString(R.string.activity_home_leave_group_dialog_message)
+ message = resources.getString(R.string.activity_home_leave_group_dialog_message)
}
} else {
- dialogMessage = resources.getString(R.string.activity_home_delete_conversation_dialog_message)
+ message = resources.getString(R.string.activity_home_delete_conversation_dialog_message)
}
val dialog = AlertDialog.Builder(this)
- dialog.setMessage(dialogMessage)
+ dialog.setMessage(message)
dialog.setPositiveButton(R.string.yes) { _, _ ->
lifecycleScope.launch(Dispatchers.Main) {
val context = this@HomeActivity as Context
-
+ // Cancel any outstanding jobs
+ DatabaseFactory.getSessionJobDatabase(context).cancelPendingMessageSendJobs(threadID)
// Send a leave group message if this is an active closed group
if (recipient.address.isClosedGroup && DatabaseFactory.getGroupDatabase(context).isActive(recipient.address.toGroupString())) {
var isClosedGroup: Boolean
@@ -350,34 +350,28 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
return@launch
}
}
-
- withContext(Dispatchers.IO) {
- val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
- val openGroupV2 = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID)
- //TODO Move open group related logic to OpenGroupUtilities / PublicChatManager / GroupManager
- if (publicChat != null) {
- val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
- apiDB.removeLastMessageServerID(publicChat.channel, publicChat.server)
- apiDB.removeLastDeletionServerID(publicChat.channel, publicChat.server)
- apiDB.clearOpenGroupProfilePictureURL(publicChat.channel, publicChat.server)
-
- OpenGroupAPI.leave(publicChat.channel, publicChat.server)
-
- ApplicationContext.getInstance(context).publicChatManager
- .removeChat(publicChat.server, publicChat.channel)
- } else if (openGroupV2 != null) {
- val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
- apiDB.removeLastMessageServerID(openGroupV2.room, openGroupV2.server)
- apiDB.removeLastDeletionServerID(openGroupV2.room, openGroupV2.server)
-
- ApplicationContext.getInstance(context).publicChatManager
- .removeChat(openGroupV2.server, openGroupV2.room)
- } else {
- threadDB.deleteConversation(threadID)
- }
- ApplicationContext.getInstance(context).messageNotifier.updateNotification(context)
+ // Delete the conversation
+ val v1OpenGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
+ val v2OpenGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID)
+ if (v1OpenGroup != null) {
+ val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
+ apiDB.removeLastMessageServerID(v1OpenGroup.channel, v1OpenGroup.server)
+ apiDB.removeLastDeletionServerID(v1OpenGroup.channel, v1OpenGroup.server)
+ apiDB.clearOpenGroupProfilePictureURL(v1OpenGroup.channel, v1OpenGroup.server)
+ OpenGroupAPI.leave(v1OpenGroup.channel, v1OpenGroup.server)
+ ApplicationContext.getInstance(context).publicChatManager
+ .removeChat(v1OpenGroup.server, v1OpenGroup.channel)
+ } else if (v2OpenGroup != null) {
+ val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
+ apiDB.removeLastMessageServerID(v2OpenGroup.room, v2OpenGroup.server)
+ apiDB.removeLastDeletionServerID(v2OpenGroup.room, v2OpenGroup.server)
+ ApplicationContext.getInstance(context).publicChatManager
+ .removeChat(v2OpenGroup.server, v2OpenGroup.room)
+ } else {
+ threadDB.deleteConversation(threadID)
}
-
+ // Update the badge count
+ ApplicationContext.getInstance(context).messageNotifier.updateNotification(context)
// Notify the user
val toastMessage = if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message
Toast.makeText(context, toastMessage, Toast.LENGTH_LONG).show()
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 f25906c167..bbe7bbe8e4 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
@@ -10,8 +10,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
+import android.widget.GridLayout
import android.widget.Toast
-import androidx.activity.viewModels
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import androidx.core.view.isVisible
import androidx.fragment.app.*
@@ -42,9 +42,6 @@ import org.thoughtcrime.securesms.loki.viewmodel.DefaultGroupsViewModel
import org.thoughtcrime.securesms.loki.viewmodel.State
class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
-
- private val viewModel by viewModels()
-
private val adapter = JoinPublicChatActivityAdapter(this)
// region Lifecycle
@@ -83,23 +80,18 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
}
fun joinPublicChatIfPossible(url: String) {
- // add http if just an IP style / host style URL is entered but leave it if scheme is included
- val properString = if (!url.startsWith("http")) "http://$url" else url
- val httpUrl = HttpUrl.parse(properString) ?: return Toast.makeText(this,R.string.invalid_url, Toast.LENGTH_SHORT).show()
-
- val room = httpUrl.pathSegments().firstOrNull()
- val publicKey = httpUrl.queryParameter("public_key")
+ // Add "http" if not entered explicitly
+ val stringWithExplicitScheme = if (!url.startsWith("http")) "http://$url" else url
+ val url = HttpUrl.parse(stringWithExplicitScheme) ?: return Toast.makeText(this,R.string.invalid_url, Toast.LENGTH_SHORT).show()
+ val room = url.pathSegments().firstOrNull()
+ val publicKey = url.queryParameter("public_key")
val isV2OpenGroup = !room.isNullOrEmpty()
showLoader()
-
lifecycleScope.launch(Dispatchers.IO) {
try {
val (threadID, groupID) = if (isV2OpenGroup) {
- val server = HttpUrl.Builder().scheme(httpUrl.scheme()).host(httpUrl.host()).apply {
- if (httpUrl.port() != 80 || httpUrl.port() != 443) {
- // non-standard port, add to server
- this.port(httpUrl.port())
- }
+ val server = HttpUrl.Builder().scheme(url.scheme()).host(url.host()).apply {
+ if (url.port() != 80 || url.port() != 443) { this.port(url.port()) } // Non-standard port; add to server
}.build()
val group = OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, server.toString().removeSuffix("/"), room!!, publicKey!!)
val threadID = GroupManager.getOpenGroupThreadID(group.id, this@JoinPublicChatActivity)
@@ -107,21 +99,19 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
threadID to groupID
} else {
val channel: Long = 1
- val group = OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, properString, channel)
+ val group = OpenGroupUtilities.addGroup(this@JoinPublicChatActivity, stringWithExplicitScheme, channel)
val threadID = GroupManager.getOpenGroupThreadID(group.id, this@JoinPublicChatActivity)
val groupID = GroupUtil.getEncodedOpenGroupID(group.id.toByteArray())
threadID to groupID
}
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity)
-
withContext(Dispatchers.Main) {
- // go to the new conversation and finish this one
- openConversationActivity(this@JoinPublicChatActivity, threadID, Recipient.from(this@JoinPublicChatActivity, Address.fromSerialized(groupID), false))
+ val recipient = Recipient.from(this@JoinPublicChatActivity, Address.fromSerialized(groupID), false)
+ openConversationActivity(this@JoinPublicChatActivity, threadID, recipient)
finish()
}
-
} catch (e: Exception) {
- Log.e("JoinPublicChatActivity", "Failed to join open group.", e)
+ Log.e("Loki", "Couldn't join open group.", e)
withContext(Dispatchers.Main) {
hideLoader()
Toast.makeText(this@JoinPublicChatActivity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
@@ -175,19 +165,40 @@ private class JoinPublicChatActivityAdapter(val activity: JoinPublicChatActivity
// region Enter Chat URL Fragment
class EnterChatURLFragment : Fragment() {
-
private val viewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_enter_chat_url, container, false)
}
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ chatURLEditText.imeOptions = chatURLEditText.imeOptions or 16777216 // Always use incognito keyboard
+ joinPublicChatButton.setOnClickListener { joinPublicChatIfPossible() }
+ viewModel.defaultRooms.observe(viewLifecycleOwner) { state ->
+ defaultRoomsContainer.isVisible = state is State.Success
+ defaultRoomsLoader.isVisible = state is State.Loading
+ when (state) {
+ State.Loading -> {
+ // TODO: Show a loader
+ }
+ is State.Error -> {
+ // TODO: Hide the loader
+ }
+ is State.Success -> {
+ populateDefaultGroups(state.value)
+ }
+ }
+ }
+ }
+
private fun populateDefaultGroups(groups: List) {
defaultRoomsGridLayout.removeAllViews()
+ defaultRoomsGridLayout.useDefaultMargins = false
groups.forEach { defaultGroup ->
- val chip = layoutInflater.inflate(R.layout.default_group_chip,defaultRoomsGridLayout, false) as Chip
+ val chip = layoutInflater.inflate(R.layout.default_group_chip, defaultRoomsGridLayout, false) as Chip
val drawable = defaultGroup.image?.let { bytes ->
- val bitmap = BitmapFactory.decodeByteArray(bytes,0,bytes.size)
+ val bitmap = BitmapFactory.decodeByteArray(bytes,0, bytes.size)
RoundedBitmapDrawableFactory.create(resources,bitmap).apply {
isCircular = true
}
@@ -197,35 +208,14 @@ class EnterChatURLFragment : Fragment() {
chip.setOnClickListener {
(requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(defaultGroup.joinURL)
}
+
defaultRoomsGridLayout.addView(chip)
}
- if (groups.size and 1 != 0) {
- // add a filler weight 1 view
+ if ((groups.size and 1) != 0) { // This checks that the number of rooms is even
layoutInflater.inflate(R.layout.grid_layout_filler, defaultRoomsGridLayout)
}
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- chatURLEditText.imeOptions = chatURLEditText.imeOptions or 16777216 // Always use incognito keyboard
- joinPublicChatButton.setOnClickListener { joinPublicChatIfPossible() }
- viewModel.defaultRooms.observe(viewLifecycleOwner) { state ->
- defaultRoomsParent.isVisible = state is State.Success
- defaultRoomsLoader.isVisible = state is State.Loading
- when (state) {
- State.Loading -> {
- // show a loader here probs
- }
- is State.Error -> {
- // hide the loader and the
- }
- is State.Success -> {
- populateDefaultGroups(state.value)
- }
- }
- }
- }
-
// region Convenience
private fun joinPublicChatIfPossible() {
val inputMethodManager = requireContext().getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/OpenGroupGuidelinesActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/OpenGroupGuidelinesActivity.kt
new file mode 100644
index 0000000000..2a9ebe5491
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/OpenGroupGuidelinesActivity.kt
@@ -0,0 +1,37 @@
+package org.thoughtcrime.securesms.loki.activities
+
+import android.os.Bundle
+import kotlinx.android.synthetic.main.activity_open_group_guidelines.*
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.BaseActionBarActivity
+
+class OpenGroupGuidelinesActivity : BaseActionBarActivity() {
+
+ // region Lifecycle
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_open_group_guidelines)
+ communityGuidelinesTextView.text = """
+ In order for our open group to be a fun environment, full of robust and constructive discussion, please follow these four simple rules:
+
+ 1. Keep conversations on-topic and add value to the discussion (no referral links, spamming, or off-topic discussion).
+
+ 2. You don't have to love everyone, but be civil (no baiting, excessively partisan arguments, threats, and so on; use common sense).
+
+ 3. Do not be a shill. Comparison and criticism is reasonable, but blatant shilling is not.
+
+ 4. Don't post explicit content, be it excessive offensive language, or content which is sexual or violent in nature.
+
+ If you break these rules, you’ll be warned by an admin. If your behaviour doesn’t improve, you will be removed from the open group.
+
+ If you see or experience any destructive behaviour, please contact an admin.
+
+ ——————————
+
+ SCAMMER WARNING
+
+ Trust only those with an admin tag in the chat. No admin will ever DM you first. No admin will ever message you for Oxen coins.
+ """.trimIndent()
+ }
+ // endregion
+}
\ No newline at end of file
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 c046fd9115..6c6d95511f 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
@@ -23,18 +23,15 @@ import network.loki.messenger.BuildConfig
import network.loki.messenger.R
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.all
-import nl.komponents.kovenant.deferred
-import nl.komponents.kovenant.functional.bind
-import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.alwaysUi
+import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.avatars.AvatarHelper
-import org.session.libsession.messaging.file_server.FileServerAPI
import org.session.libsession.messaging.open_groups.OpenGroupAPI
import org.session.libsession.messaging.threads.Address
+import org.session.libsession.utilities.ProfilePictureUtilities
import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.preferences.ProfileKeyUtil
-import org.session.libsignal.service.api.util.StreamDetails
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.avatar.AvatarSelection
@@ -51,7 +48,6 @@ import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
import org.thoughtcrime.securesms.util.BitmapDecodingException
import org.thoughtcrime.securesms.util.BitmapUtil
-import java.io.ByteArrayInputStream
import java.io.File
import java.security.SecureRandom
import java.util.*
@@ -127,7 +123,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
AvatarSelection.REQUEST_CODE_AVATAR -> {
- if (resultCode != Activity.RESULT_OK) { return }
+ if (resultCode != Activity.RESULT_OK) {
+ return
+ }
val outputFile = Uri.fromFile(File(cacheDir, "cropped"))
var inputFile: Uri? = data?.data
if (inputFile == null && tempFile != null) {
@@ -136,7 +134,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.CropImageActivity_profile_avatar)
}
AvatarSelection.REQUEST_CODE_CROP_IMAGE -> {
- if (resultCode != Activity.RESULT_OK) { return }
+ if (resultCode != Activity.RESULT_OK) {
+ return
+ }
AsyncTask.execute {
try {
profilePictureToBeUploaded = BitmapUtil.createScaledBytes(this@SettingsActivity, AvatarSelection.getResultUri(data), ProfileMediaConstraints()).bitmap
@@ -186,42 +186,28 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
}
val profilePicture = profilePictureToBeUploaded
val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this)
- val profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey)
if (isUpdatingProfilePicture && profilePicture != null) {
- val storageAPI = FileServerAPI.shared
- val deferred = deferred()
- AsyncTask.execute {
- val stream = StreamDetails(ByteArrayInputStream(profilePicture), "image/jpeg", profilePicture.size.toLong())
- val (_, url) = storageAPI.uploadProfilePicture(storageAPI.server, profileKey, stream) {
- TextSecurePreferences.setLastProfilePictureUpload(this@SettingsActivity, Date().time)
- }
- TextSecurePreferences.setProfilePictureURL(this, url)
- deferred.resolve(Unit)
- }
- promises.add(deferred.promise)
+ promises.add(ProfilePictureUtilities.upload(profilePicture, encodedProfileKey, this))
}
-
- all(promises).bind {
- // updating the profile name or picture
- if (profilePicture != null || displayName != null) {
- task {
- if (isUpdatingProfilePicture && profilePicture != null) {
- AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
- TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt())
- ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
- ApplicationContext.getInstance(this).updateOpenGroupProfilePicturesIfNeeded()
- }
- MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
- }
- } else {
- Promise.of(Unit)
+ val compoundPromise = all(promises)
+ compoundPromise.successUi { // Do this on the UI thread so that it happens before the alwaysUi clause below
+ if (isUpdatingProfilePicture && profilePicture != null) {
+ AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
+ TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt())
+ TextSecurePreferences.setLastProfilePictureUpload(this, Date().time)
+ ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
+ ApplicationContext.getInstance(this).updateOpenGroupProfilePicturesIfNeeded()
}
- }.alwaysUi {
+ if (profilePicture != null || displayName != null) {
+ MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
+ }
+ }
+ compoundPromise.alwaysUi {
if (displayName != null) {
btnGroupNameDisplay.text = displayName
}
if (isUpdatingProfilePicture && profilePicture != null) {
- profilePictureView.recycle() // clear cached image before update tje profilePictureView
+ profilePictureView.recycle() // Clear the cached image before updating
profilePictureView.update()
}
displayNameToBeUploaded = null
diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt
index 22878d46fd..c270797c6c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatManager.kt
@@ -11,6 +11,7 @@ import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPolle
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupV2Poller
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.Util
+import org.session.libsignal.utilities.ThreadUtils
import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager
@@ -138,9 +139,10 @@ class PublicChatManager(private val context: Context) {
val groupId = OpenGroup.getId(channel, server)
val threadId = GroupManager.getOpenGroupThreadID(groupId, context)
val groupAddress = threadDB.getRecipientForThreadId(threadId)!!.address.serialize()
- GroupManager.deleteGroup(groupAddress, context)
-
- Util.runOnMain { startPollersIfNeeded() }
+ ThreadUtils.queue {
+ GroupManager.deleteGroup(groupAddress, context) // Must be invoked on a background thread
+ Util.runOnMain { startPollersIfNeeded() }
+ }
}
fun removeChat(server: String, room: String) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt
index 85a66b159c..83dc565117 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.database
import android.content.ContentValues
import android.content.Context
+import net.sqlcipher.database.SQLiteDatabase.CONFLICT_REPLACE
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
@@ -98,11 +99,11 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
override fun setServerID(messageID: Long, serverID: Long, isSms: Boolean) {
val database = databaseHelper.writableDatabase
- val contentValues = ContentValues(2)
+ val contentValues = ContentValues(3)
contentValues.put(Companion.messageID, messageID)
contentValues.put(Companion.serverID, serverID)
contentValues.put(messageType, if (isSms) SMS_TYPE else MMS_TYPE)
- database.insertOrUpdate(messageIDTable, contentValues, "${Companion.messageID} = ? AND ${Companion.serverID} = ?", arrayOf(messageID.toString(), serverID.toString()))
+ database.insertWithOnConflict(messageIDTable, null, contentValues, CONFLICT_REPLACE)
}
fun getOriginalThreadID(messageID: Long): Long {
@@ -114,11 +115,11 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
fun setOriginalThreadID(messageID: Long, serverID: Long, threadID: Long) {
val database = databaseHelper.writableDatabase
- val contentValues = ContentValues(2)
+ val contentValues = ContentValues(3)
contentValues.put(Companion.messageID, messageID)
contentValues.put(Companion.serverID, serverID)
contentValues.put(Companion.threadID, threadID)
- database.insertOrUpdate(messageThreadMappingTable, contentValues, "${Companion.messageID} = ? AND ${Companion.serverID} = ?", arrayOf(messageID.toString(), serverID.toString()))
+ database.insertWithOnConflict(messageThreadMappingTable, null, contentValues, CONFLICT_REPLACE)
}
fun getErrorMessage(messageID: Long): String? {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt
index 22c8d48f44..c5e5e102ce 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionJobDatabase.kt
@@ -71,6 +71,30 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
}
}
+ fun cancelPendingMessageSendJobs(threadID: Long) {
+ val database = databaseHelper.writableDatabase
+ val attachmentUploadJobKeys = mutableListOf()
+ database.getAll(sessionJobTable, "$jobType = ?", arrayOf( AttachmentUploadJob.KEY )) { cursor ->
+ val job = jobFromCursor(cursor) as AttachmentUploadJob?
+ if (job != null && job.threadID == threadID.toString()) { attachmentUploadJobKeys.add(job.id!!) }
+ }
+ val messageSendJobKeys = mutableListOf()
+ database.getAll(sessionJobTable, "$jobType = ?", arrayOf( MessageSendJob.KEY )) { cursor ->
+ val job = jobFromCursor(cursor) as MessageSendJob?
+ if (job != null && job.message.threadID == threadID) { messageSendJobKeys.add(job.id!!) }
+ }
+ if (attachmentUploadJobKeys.isNotEmpty()) {
+ val attachmentUploadJobKeysAsString = attachmentUploadJobKeys.joinToString(", ")
+ database.delete(sessionJobTable, "${Companion.jobType} = ? AND ${Companion.jobID} IN (?)",
+ arrayOf( AttachmentUploadJob.KEY, attachmentUploadJobKeysAsString ))
+ }
+ if (messageSendJobKeys.isNotEmpty()) {
+ val messageSendJobKeysAsString = messageSendJobKeys.joinToString(", ")
+ database.delete(sessionJobTable, "${Companion.jobType} = ? AND ${Companion.jobID} IN (?)",
+ arrayOf( MessageSendJob.KEY, messageSendJobKeysAsString ))
+ }
+ }
+
fun isJobCanceled(job: Job): Boolean {
val database = databaseHelper.readableDatabase
var cursor: android.database.Cursor? = null
diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt
index 5f61506504..745e9067f9 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki.protocol
import android.content.Context
import android.util.Log
import com.google.protobuf.ByteString
+import org.session.libsession.messaging.sending_receiving.*
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
import org.session.libsignal.libsignal.ecc.ECKeyPair
@@ -15,12 +16,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation
-import org.thoughtcrime.securesms.loki.api.SessionProtocolImpl
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase
-import org.session.libsession.messaging.sending_receiving.MessageSender
-import org.session.libsession.messaging.sending_receiving.generateAndSendNewEncryptionKeyPair
-import org.session.libsession.messaging.sending_receiving.pendingKeyPair
-import org.session.libsession.messaging.sending_receiving.sendEncryptionKeyPair
import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.GroupRecord
@@ -195,7 +191,7 @@ object ClosedGroupsProtocolV2 {
}
if (userPublicKey in admins) {
// send current encryption key to the latest added members
- val encryptionKeyPair = pendingKeyPair[groupPublicKey]?.orNull()
+ val encryptionKeyPair = pendingKeyPairs[groupPublicKey]?.orNull()
?: apiDB.getLatestClosedGroupEncryptionKeyPair(groupPublicKey)
if (encryptionKeyPair == null) {
Log.d("Loki", "Couldn't get encryption key pair for closed group.")
@@ -330,7 +326,7 @@ object ClosedGroupsProtocolV2 {
// Find our wrapper and decrypt it if possible
val wrapper = closedGroupUpdate.wrappersList.firstOrNull { it.publicKey.toByteArray().toHexString() == userPublicKey } ?: return
val encryptedKeyPair = wrapper.encryptedKeyPair.toByteArray()
- val plaintext = SessionProtocolImpl(context).decrypt(encryptedKeyPair, userKeyPair).first
+ val plaintext = MessageDecrypter.decrypt(encryptedKeyPair, userKeyPair).first
// Parse it
val proto = SignalServiceProtos.KeyPair.parseFrom(plaintext)
val keyPair = ECKeyPair(DjbECPublicKey(proto.publicKey.toByteArray().removing05PrefixIfNeeded()), DjbECPrivateKey(proto.privateKey.toByteArray()))
diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt
index cf8c747638..66d7263747 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt
@@ -35,11 +35,10 @@ object OpenGroupUtilities {
val groupInfo = OpenGroupAPIV2.getInfo(room,server).get()
val application = ApplicationContext.getInstance(context)
- val group = application.publicChatManager.addChat(server, room, groupInfo, publicKey)
-
val storage = MessagingModuleConfiguration.shared.storage
storage.removeLastDeletionServerId(room, server)
storage.removeLastMessageServerId(room, server)
+ val group = application.publicChatManager.addChat(server, room, groupInfo, publicKey)
return group
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/DefaultGroupsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/DefaultGroupsViewModel.kt
index a2d747ed9b..d4281a6cfb 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/DefaultGroupsViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/loki/viewmodel/DefaultGroupsViewModel.kt
@@ -20,5 +20,4 @@ class DefaultGroupsViewModel : ViewModel() {
}.onStart {
emit(State.Loading)
}.asLiveData()
-
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/OpenGroupGuidelinesView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/OpenGroupGuidelinesView.kt
new file mode 100644
index 0000000000..d29460866c
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/OpenGroupGuidelinesView.kt
@@ -0,0 +1,38 @@
+package org.thoughtcrime.securesms.loki.views
+
+import android.content.Context
+import android.content.Intent
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.FrameLayout
+import kotlinx.android.synthetic.main.view_open_group_guidelines.view.*
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.conversation.ConversationActivity
+import org.thoughtcrime.securesms.loki.activities.OpenGroupGuidelinesActivity
+import org.thoughtcrime.securesms.loki.utilities.push
+
+class OpenGroupGuidelinesView : FrameLayout {
+
+ constructor(context: Context) : super(context) {
+ setUpViewHierarchy()
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ setUpViewHierarchy()
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ setUpViewHierarchy()
+ }
+
+ private fun setUpViewHierarchy() {
+ val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ val contentView = inflater.inflate(R.layout.view_open_group_guidelines, null)
+ addView(contentView)
+ readButton.setOnClickListener {
+ val activity = context as ConversationActivity
+ val intent = Intent(activity, OpenGroupGuidelinesActivity::class.java)
+ activity.push(intent)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout-sw400dp/activity_pn_mode.xml b/app/src/main/res/layout-sw400dp/activity_pn_mode.xml
index e31e269a2e..fb1fc1c6ca 100644
--- a/app/src/main/res/layout-sw400dp/activity_pn_mode.xml
+++ b/app/src/main/res/layout-sw400dp/activity_pn_mode.xml
@@ -56,7 +56,7 @@
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
- android:text="You’ll be notified of new messages reliably and immediately using Google’s notification servers. The contents of your messages, and who you’re messaging, are never exposed to Google." />
+ android:text="You’ll be notified of new messages reliably and immediately using Google’s notification servers." />
+ android:text="Session will occasionally check for new messages in the background." />
diff --git a/app/src/main/res/layout-sw400dp/fragment_enter_chat_url.xml b/app/src/main/res/layout-sw400dp/fragment_enter_chat_url.xml
index a4b088aac1..a011b2bf4b 100644
--- a/app/src/main/res/layout-sw400dp/fragment_enter_chat_url.xml
+++ b/app/src/main/res/layout-sw400dp/fragment_enter_chat_url.xml
@@ -33,23 +33,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_pn_mode.xml b/app/src/main/res/layout/activity_pn_mode.xml
index 2e8f12b7f2..b86c0a3221 100644
--- a/app/src/main/res/layout/activity_pn_mode.xml
+++ b/app/src/main/res/layout/activity_pn_mode.xml
@@ -56,7 +56,7 @@
android:layout_marginTop="4dp"
android:textSize="@dimen/very_small_font_size"
android:textColor="@color/text"
- android:text="You’ll be notified of new messages reliably and immediately using Google’s notification servers. The contents of your messages, and who you’re messaging, are never exposed to Google." />
+ android:text="You’ll be notified of new messages reliably and immediately using Google’s notification servers." />
+ android:text="Session will occasionally check for new messages in the background." />
diff --git a/app/src/main/res/layout/conversation_activity.xml b/app/src/main/res/layout/conversation_activity.xml
index 0b9ff3d077..ba9f7f27e1 100644
--- a/app/src/main/res/layout/conversation_activity.xml
+++ b/app/src/main/res/layout/conversation_activity.xml
@@ -134,6 +134,12 @@
android:background="?android:dividerHorizontal"
android:elevation="1dp" />
+
+
-
\ No newline at end of file
+ android:layout_height="wrap_content" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_enter_chat_url.xml b/app/src/main/res/layout/fragment_enter_chat_url.xml
index 7affed157e..462c99e434 100644
--- a/app/src/main/res/layout/fragment_enter_chat_url.xml
+++ b/app/src/main/res/layout/fragment_enter_chat_url.xml
@@ -33,23 +33,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-notnight-v21/colors.xml b/app/src/main/res/values-notnight-v21/colors.xml
index 4d90ff3f87..36e4a1858d 100644
--- a/app/src/main/res/values-notnight-v21/colors.xml
+++ b/app/src/main/res/values-notnight-v21/colors.xml
@@ -22,6 +22,7 @@
#F5F5F5
#FCFCFC
#F5F5F5
+ #0D000000
#ffffff
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index ecbcd77ec7..8771cdae4e 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -31,6 +31,7 @@
#1B1B1B
#212121
#FFCE3A
+ #0DFFFFFF
- #5ff8b0
diff --git a/app/src/main/res/xml/preferences_notifications.xml b/app/src/main/res/xml/preferences_notifications.xml
index a1b33553ac..76fff3f6d6 100644
--- a/app/src/main/res/xml/preferences_notifications.xml
+++ b/app/src/main/res/xml/preferences_notifications.xml
@@ -27,7 +27,7 @@
android:dependency="pref_key_enable_notifications"
android:key="pref_key_use_fcm"
android:title="Use Fast Mode"
- android:summary="You’ll be notified of new messages reliably and immediately using Google’s notification servers. The contents of your messages, and who you’re messaging, are never exposed to Google."
+ android:summary="You’ll be notified of new messages reliably and immediately using Google’s notification servers."
android:defaultValue="false" />
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 b34a4ac3be..ba33ec5a58 100644
--- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt
+++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt
@@ -29,8 +29,8 @@ interface MessageDataProvider {
fun isOutgoingMessage(timestamp: Long): Boolean
- fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult)
- fun updateAttachmentAfterUploadFailed(attachmentId: Long)
+ fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult)
+ fun handleFailedAttachmentUpload(attachmentId: Long)
fun getMessageForQuote(timestamp: Long, author: Address): Pair?
fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List
diff --git a/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt b/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt
index 68610f9638..98a9588332 100644
--- a/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt
+++ b/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt
@@ -2,13 +2,11 @@ package org.session.libsession.messaging
import android.content.Context
import org.session.libsession.database.MessageDataProvider
-import org.session.libsignal.service.loki.api.crypto.SessionProtocol
class MessagingModuleConfiguration(
val context: Context,
val storage: StorageProtocol,
- val messageDataProvider: MessageDataProvider,
- val sessionProtocol: SessionProtocol
+ val messageDataProvider: MessageDataProvider
) {
companion object {
@@ -16,11 +14,10 @@ class MessagingModuleConfiguration(
fun configure(context: Context,
storage: StorageProtocol,
- messageDataProvider: MessageDataProvider,
- sessionProtocol: SessionProtocol
+ messageDataProvider: MessageDataProvider
) {
if (Companion::shared.isInitialized) { return }
- shared = MessagingModuleConfiguration(context, storage, messageDataProvider, sessionProtocol)
+ shared = MessagingModuleConfiguration(context, storage, messageDataProvider)
}
}
}
\ No newline at end of file
diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt
index d850e30664..816ede3414 100644
--- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt
+++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt
@@ -92,7 +92,7 @@ interface StorageProtocol {
fun removeLastDeletionServerId(room: String, server: String)
// Message Handling
- fun isMessageDuplicated(timestamp: Long, sender: String): Boolean
+ fun isDuplicateMessage(timestamp: Long, sender: String): Boolean
fun getReceivedMessageTimestamps(): Set
fun addReceivedMessageTimestamp(timestamp: Long)
fun removeReceivedMessageTimestamps(timestamps: Set)
diff --git a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPIV2.kt
index c8db066692..89bea6bc88 100644
--- a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPIV2.kt
+++ b/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPIV2.kt
@@ -15,8 +15,8 @@ import org.session.libsignal.utilities.logging.Log
object FileServerAPIV2 {
- private const val DEFAULT_SERVER_PUBLIC_KEY = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69"
- const val DEFAULT_SERVER = "http://88.99.175.227"
+ private const val SERVER_PUBLIC_KEY = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69"
+ const val SERVER = "http://88.99.175.227"
sealed class Error(message: String) : Exception(message) {
object ParsingFailed : Error("Invalid response.")
@@ -43,7 +43,7 @@ object FileServerAPIV2 {
}
private fun send(request: Request): Promise