diff --git a/app/build.gradle b/app/build.gradle index 26a37e8200..cf772c0a75 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,7 +143,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 184 +def canonicalVersionCode = 186 def canonicalVersionName = "1.11.0" def postFixSize = 10 diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java index a4a17b7389..8f820cba54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java @@ -39,6 +39,7 @@ 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; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.loki.fragments.ContactSelectionListFragment; @@ -215,9 +216,9 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity } private void createConversation(long threadId, Address address, int distributionType) { - final Intent intent = getBaseShareIntent(ConversationActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, address); - intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); + 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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java index ac5ee5004c..b93ae4ab65 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java @@ -14,6 +14,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.service.ExpiringMessageManager; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java index f288ac5ba5..ece1bb784d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java @@ -14,6 +14,7 @@ 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; @@ -80,9 +81,9 @@ public class ConversationPopupActivity extends ConversationActivity { @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, ConversationActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, getRecipient().getAddress()); - intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, result); + 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()); 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 3065ca13cc..e2abbff332 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 @@ -18,10 +18,14 @@ import android.util.Log import android.util.Pair import android.util.TypedValue import android.view.* +import android.widget.LinearLayout import android.widget.RelativeLayout import android.widget.Toast +import androidx.annotation.DimenRes import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import androidx.loader.app.LoaderManager import androidx.loader.content.Loader @@ -57,16 +61,21 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPrevi import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.messaging.utilities.UpdateMessageData.Companion.fromJSON +import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.MediaTypes import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.concurrent.SimpleTask import org.session.libsession.utilities.recipients.Recipient +import org.session.libsession.utilities.recipients.RecipientModifiedListener import org.session.libsignal.utilities.ListenableFuture -import org.session.libsignal.utilities.ThreadUtils +import org.session.libsignal.utilities.SettableFuture +import org.session.libsignal.utilities.guava.Optional 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 @@ -75,7 +84,10 @@ import org.thoughtcrime.securesms.conversation.v2.input_bar.mentions.MentionCand import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallback import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallbackDelegate import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper +import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView +import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar +import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DraftDatabase @@ -110,8 +122,9 @@ import kotlin.math.* // price we pay is a bit of back and forth between the input bar and the conversation activity. class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate, - InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher, - ConversationActionModeCallbackDelegate { + InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher, + ConversationActionModeCallbackDelegate, VisibleMessageContentViewDelegate, RecipientModifiedListener, + SearchBottomBar.EventListener { private val screenWidth = Resources.getSystem().displayMetrics.widthPixels private var linkPreviewViewModel: LinkPreviewViewModel? = null private var threadID: Long = -1 @@ -130,6 +143,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private var previousText: CharSequence = "" private var currentMentionStartIndex = -1 private var isShowingMentionCandidatesView = false + // Search + var searchViewModel: SearchViewModel? = null + var searchViewItem: MenuItem? = null + + private val isScrolledToBottom: Boolean + get() { + val position = layoutManager.findFirstCompletelyVisibleItemPosition() + return position == 0 + } private val layoutManager: LinearLayoutManager get() { return conversationRecyclerView.layoutManager as LinearLayoutManager } @@ -150,6 +172,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe }, glide ) + adapter.visibleMessageContentViewDelegate = this adapter } @@ -166,7 +189,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // region Settings companion object { + // Extras const val THREAD_ID = "thread_id" + const val ADDRESS = "address" + // Request codes const val PICK_DOCUMENT = 2 const val TAKE_PHOTO = 7 const val PICK_GIF = 10 @@ -179,7 +205,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { super.onCreate(savedInstanceState, isReady) setContentView(R.layout.activity_conversation_v2) - threadID = intent.getLongExtra(THREAD_ID, -1) + var threadID = intent.getLongExtra(THREAD_ID, -1L) + if (threadID == -1L) { + val address = intent.getParcelableExtra
(ADDRESS) ?: return finish() + val recipient = Recipient.from(this, address, false) + threadID = DatabaseFactory.getThreadDatabase(this).getOrCreateThreadIdFor(recipient) + } + this.threadID = threadID setUpRecyclerView() setUpToolBar() setUpInputBar() @@ -189,12 +221,16 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe unreadCount = DatabaseFactory.getMmsSmsDatabase(this).getUnreadCount(threadID) updateUnreadCountIndicator() setUpTypingObserver() + setUpRecipientObserver() updateSubtitle() getLatestOpenGroupInfoIfNeeded() setUpBlockedBanner() setUpLinkPreviewObserver() + searchBottomBar.setEventListener(this) + setUpSearchResultObserver() scrollToFirstUnreadMessageIfNeeded() markAllAsRead() + showOrHideInputIfNeeded() } override fun onResume() { @@ -251,6 +287,14 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe actionBar.setCustomView(R.layout.activity_conversation_v2_action_bar) actionBar.setDisplayShowCustomEnabled(true) conversationTitleView.text = thread.toShortString() + @DimenRes val sizeID: Int + if (thread.isClosedGroupRecipient) { + sizeID = R.dimen.medium_profile_picture_size + } else { + sizeID = R.dimen.small_profile_picture_size + } + val size = resources.getDimension(sizeID).roundToInt() + profilePictureView.layoutParams = LinearLayout.LayoutParams(size, size) profilePictureView.glide = glide profilePictureView.update(thread, threadID) } @@ -281,6 +325,27 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun restoreDraftIfNeeded() { + val mediaURI = intent.data + val mediaType = AttachmentManager.MediaType.from(intent.type) + if (mediaURI != null && mediaType != null) { + if (AttachmentManager.MediaType.IMAGE == mediaType || AttachmentManager.MediaType.GIF == mediaType || AttachmentManager.MediaType.VIDEO == mediaType) { + val media = Media(mediaURI, MediaUtil.getMimeType(this, mediaURI)!!, 0, 0, 0, 0, Optional.absent(), Optional.absent()) + startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), thread, ""), ConversationActivityV2.PICK_FROM_LIBRARY) + return + } else { + prepMediaForSending(mediaURI, mediaType).addListener(object : ListenableFuture.Listener { + + override fun onSuccess(result: Boolean?) { + sendAttachments(attachmentManager.buildSlideDeck().asAttachments(), null) + } + + override fun onFailure(e: ExecutionException?) { + Toast.makeText(this@ConversationActivityV2, R.string.activity_conversation_attachment_prep_failed, Toast.LENGTH_LONG).show() + } + }) + return + } + } val draftDB = DatabaseFactory.getDraftDatabase(this) val drafts = draftDB.getDrafts(threadID) draftDB.clearDrafts(threadID) @@ -302,7 +367,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private fun setUpTypingObserver() { ApplicationContext.getInstance(this).typingStatusRepository.getTypists(threadID).observe(this) { state -> val recipients = if (state != null) state.typists else listOf() - typingIndicatorViewContainer.isVisible = recipients.isNotEmpty() + // FIXME: Also checking isScrolledToBottom is a quick fix for an issue where the + // typing indicator overlays the recycler view when scrolled up + typingIndicatorViewContainer.isVisible = recipients.isNotEmpty() && isScrolledToBottom typingIndicatorViewContainer.setTypists(recipients) inputBarHeightChanged(inputBar.height) } @@ -316,6 +383,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } + private fun setUpRecipientObserver() { + thread.addListener(this) + } + private fun getLatestOpenGroupInfoIfNeeded() { val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID) ?: return OpenGroupAPIV2.getMemberCount(openGroup.room, openGroup.server).successUi { updateSubtitle() } @@ -358,7 +429,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun onPrepareOptionsMenu(menu: Menu): Boolean { - ConversationMenuHelper.onPrepareOptionsMenu(menu, menuInflater, thread, this) { onOptionsItemSelected(it) } + ConversationMenuHelper.onPrepareOptionsMenu(menu, menuInflater, thread, threadID, this) { onOptionsItemSelected(it) } super.onPrepareOptionsMenu(menu) return true } @@ -369,7 +440,28 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } // endregion - // region Updating & Animation + // region Animation & Updating + override fun onModified(recipient: Recipient) { + runOnUiThread { + if (thread.isContactRecipient) { + blockedBanner.isVisible = thread.isBlocked + } + updateSubtitle() + showOrHideInputIfNeeded() + } + } + + private fun showOrHideInputIfNeeded() { + if (thread.isClosedGroupRecipient) { + val group = DatabaseFactory.getGroupDatabase(this).getGroup(thread.address.toGroupString()).orNull() + val isActive = (group?.isActive == true) + Log.d("Test", "isActive: $isActive") + inputBar.showInput = isActive + } else { + inputBar.showInput = true + } + } + private fun markAllAsRead() { val messages = DatabaseFactory.getThreadDatabase(this).setRead(threadID, true) if (thread.isGroupRecipient) { @@ -570,8 +662,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun handleRecyclerViewScrolled() { - val position = layoutManager.findFirstCompletelyVisibleItemPosition() - val alpha = if (position > 0) 1.0f else 0.0f + val alpha = if (!isScrolledToBottom) 1.0f else 0.0f + // FIXME: Checking isScrolledToBottom is a quick fix for an issue where the + // typing indicator overlays the recycler view when scrolled up + val wasTypingIndicatorVisibleBefore = typingIndicatorViewContainer.isVisible + typingIndicatorViewContainer.isVisible = wasTypingIndicatorVisibleBefore && isScrolledToBottom + val isTypingIndicatorVisibleAfter = typingIndicatorViewContainer.isVisible + if (isTypingIndicatorVisibleAfter != wasTypingIndicatorVisibleBefore) { + inputBarHeightChanged(inputBar.height) + } scrollToBottomButton.alpha = alpha unreadCount = min(unreadCount, layoutManager.findFirstVisibleItemPosition()) updateUnreadCountIndicator() @@ -644,6 +743,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val actionMode = this.actionMode val actionModeCallback = ConversationActionModeCallback(adapter, threadID, this) actionModeCallback.delegate = this + searchViewItem?.collapseActionView() if (actionMode == null) { // Nothing should be selected if this is the case adapter.toggleSelection(message, position) this.actionMode = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { @@ -740,6 +840,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe this.previousText = newText } + override fun scrollToMessageIfPossible(timestamp: Long) { + val lastSeenItemPosition = adapter.getItemPositionForTimestamp(timestamp) ?: return + conversationRecyclerView.scrollToPosition(lastSeenItemPosition) + } + override fun sendMessage() { if (thread.isContactRecipient && thread.isBlocked) { BlockedDialog(thread).show(supportFragmentManager, "Blocked Dialog") @@ -908,10 +1013,18 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun startRecordingVoiceMessage() { - showVoiceMessageUI() - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - audioRecorder.startRecording() - stopAudioHandler.postDelayed(stopVoiceMessageRecordingTask, 60000) // Limit voice messages to 1 minute each + if (Permissions.hasAll(this, Manifest.permission.RECORD_AUDIO)) { + showVoiceMessageUI() + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + audioRecorder.startRecording() + stopAudioHandler.postDelayed(stopVoiceMessageRecordingTask, 60000) // Limit voice messages to 1 minute each + } else { + 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 fun sendVoiceMessage() { @@ -966,13 +1079,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } } else { - ThreadUtils.queue { - for (message in messages) { - if (message.isMms) { - DatabaseFactory.getMmsDatabase(this@ConversationActivityV2).delete(message.id) - } else { - DatabaseFactory.getSmsDatabase(this@ConversationActivityV2).deleteMessage(message.id) - } + for (message in messages) { + if (message.isMms) { + DatabaseFactory.getMmsDatabase(this@ConversationActivityV2).delete(message.id) + } else { + DatabaseFactory.getSmsDatabase(this@ConversationActivityV2).deleteMessage(message.id) } } } @@ -1157,4 +1268,46 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe draftDB.insertDrafts(threadID, drafts) } // endregion -} + + // region Search + private fun setUpSearchResultObserver() { + val searchViewModel = ViewModelProvider(this).get(SearchViewModel::class.java) + this.searchViewModel = searchViewModel + searchViewModel.searchResults.observe(this, Observer { result: SearchViewModel.SearchResult? -> + if (result == null) return@Observer + if (result.getResults().isNotEmpty()) { + result.getResults()[result.position]?.let { + jumpToMessage(it.messageRecipient.address, it.receivedTimestampMs, Runnable { searchViewModel.onMissingResult() }) + } + } + this.searchBottomBar.setData(result.position, result.getResults().size) + }) + } + + fun onSearchQueryUpdated(query: String?) { + adapter.onSearchQueryUpdated(query) + } + + override fun onSearchMoveUpPressed() { + this.searchViewModel?.onMoveUp() + } + + override fun onSearchMoveDownPressed() { + this.searchViewModel?.onMoveDown() + } + + private fun jumpToMessage(author: Address, timestamp: Long, onMessageNotFound: Runnable?) { + SimpleTask.run(lifecycle, { + DatabaseFactory.getMmsSmsDatabase(this).getMessagePositionInConversation(threadID, timestamp, author) + }) { p: Int -> moveToMessagePosition(p, onMessageNotFound) } + } + + private fun moveToMessagePosition(position: Int, onMessageNotFound: Runnable?) { + if (position >= 0) { + conversationRecyclerView.scrollToPosition(position) + } else { + onMessageNotFound?.run() + } + } + // endregion +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 9b9b9e2c59..4b413ef89f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -9,6 +9,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView.ViewHolder import kotlinx.android.synthetic.main.view_visible_message.view.* import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView +import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.DatabaseFactory @@ -21,6 +22,8 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr : CursorRecyclerViewAdapter(context, cursor) { private val messageDB = DatabaseFactory.getMmsSmsDatabase(context) var selectedItems = mutableSetOf() + private var searchQuery: String? = null + var visibleMessageContentViewDelegate: VisibleMessageContentViewDelegate? = null sealed class ViewType(val rawValue: Int) { object Visible : ViewType(0) @@ -69,10 +72,11 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr view.snIsSelected = isSelected view.messageTimestampTextView.isVisible = isSelected val position = viewHolder.adapterPosition - view.bind(message, getMessageBefore(position, cursor), getMessageAfter(position, cursor), glide) + view.bind(message, getMessageBefore(position, cursor), getMessageAfter(position, cursor), glide, searchQuery) view.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, view, event) } view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) } view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) } + view.contentViewDelegate = visibleMessageContentViewDelegate } is ControlMessageViewHolder -> viewHolder.view.bind(message) } @@ -114,9 +118,25 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr if (lastSeenTimestamp <= 0L || cursor == null || !isActiveCursor) return null for (i in 0 until itemCount) { cursor.moveToPosition(i) - val messageRecord = messageDB.readerFor(cursor).current - if (messageRecord.isOutgoing || messageRecord.dateReceived <= lastSeenTimestamp) { return i } + val message = messageDB.readerFor(cursor).current + if (message.isOutgoing || message.dateReceived <= lastSeenTimestamp) { return i } } return null } + + fun getItemPositionForTimestamp(timestamp: Long): Int? { + val cursor = this.cursor + if (timestamp <= 0L || cursor == null || !isActiveCursor) return null + for (i in 0 until itemCount) { + cursor.moveToPosition(i) + val message = messageDB.readerFor(cursor).current + if (message.dateSent == timestamp) { return i } + } + return null + } + + fun onSearchQueryUpdated(query: String?) { + this.searchQuery = query + notifyDataSetChanged() + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ExpirationTimerView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.java similarity index 98% rename from app/src/main/java/org/thoughtcrime/securesms/components/ExpirationTimerView.java rename to app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.java index 65cad0a274..5a04e77ac2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ExpirationTimerView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.java @@ -1,4 +1,4 @@ -package org.thoughtcrime.securesms.components; +package org.thoughtcrime.securesms.conversation.v2.components; import android.content.Context; import androidx.annotation.NonNull; @@ -118,5 +118,4 @@ public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImag Util.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn)); } } - } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt index 4b89e9d80b..51d85c3651 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/JoinOpenGroupDialog.kt @@ -6,10 +6,12 @@ import android.text.SpannableStringBuilder import android.text.style.StyleSpan import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.dialog_join_open_group.view.* import network.loki.messenger.R import org.session.libsession.messaging.open_groups.OpenGroupV2 import org.session.libsession.utilities.OpenGroupUrlParser +import org.session.libsignal.utilities.ThreadUtils import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog import org.thoughtcrime.securesms.loki.api.OpenGroupManager import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol @@ -33,8 +35,11 @@ class JoinOpenGroupDialog(private val name: String, private val url: String) : B private fun join() { val openGroup = OpenGroupUrlParser.parseUrl(url) - OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, requireContext()) - MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(requireContext()) + val activity = requireContext() as AppCompatActivity + ThreadUtils.queue { + OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, activity) + MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(activity) + } dismiss() } } \ No newline at end of file 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 d77a17efca..10389ca453 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 @@ -32,6 +32,8 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li var additionalContentHeight = 0 var quote: MessageRecord? = null var linkPreview: LinkPreview? = null + var showInput: Boolean = true + set(value) { field = value; showOrHideInputIfNeeded() } var text: String get() { return inputBarEditText.text.toString() } @@ -161,6 +163,19 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li additionalContentHeight = 0 setHeight(newHeight) } + + private fun showOrHideInputIfNeeded() { + if (showInput) { + setOf( inputBarEditText, attachmentsButton ).forEach { it.isVisible = true } + microphoneButton.isVisible = text.isEmpty() + sendButton.isVisible = text.isNotEmpty() + } else { + cancelQuoteDraft() + cancelLinkPreviewDraft() + val views = setOf( inputBarEditText, attachmentsButton, microphoneButton, sendButton ) + views.forEach { it.isVisible = false } + } + } // endregion } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt index cbac59c280..5c26c4d540 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt @@ -12,17 +12,20 @@ import android.os.AsyncTask import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.View import android.widget.ImageView import android.widget.TextView import android.widget.Toast import androidx.annotation.ColorInt import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SearchView +import androidx.appcompat.widget.SearchView.OnQueryTextListener import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat +import kotlinx.android.synthetic.main.activity_conversation_v2.* import network.loki.messenger.R -import org.session.libsession.avatars.ContactPhoto import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.leave @@ -30,11 +33,9 @@ import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.GroupUtil.doubleDecodeGroupID import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient -import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.* -import org.thoughtcrime.securesms.conversation.ConversationActivity import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity @@ -43,11 +44,10 @@ import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity import org.thoughtcrime.securesms.loki.utilities.getColorWithID import org.thoughtcrime.securesms.util.BitmapUtil import java.io.IOException -import java.lang.ref.WeakReference object ConversationMenuHelper { - fun onPrepareOptionsMenu(menu: Menu, inflater: MenuInflater, thread: Recipient, context: Context, onOptionsItemSelected: (MenuItem) -> Unit) { + fun onPrepareOptionsMenu(menu: Menu, inflater: MenuInflater, thread: Recipient, threadId: Long, context: Context, onOptionsItemSelected: (MenuItem) -> Unit) { // Prepare menu.clear() val isOpenGroup = thread.isOpenGroupRecipient @@ -92,6 +92,49 @@ object ConversationMenuHelper { } else { inflater.inflate(R.menu.menu_conversation_unmuted, menu) } + + // Search + val searchViewItem = menu.findItem(R.id.menu_search) + (context as ConversationActivityV2).searchViewItem = searchViewItem + val searchView = searchViewItem.actionView as SearchView + val searchViewModel = context.searchViewModel!! + val queryListener = object : OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + return true + } + + override fun onQueryTextChange(query: String): Boolean { + searchViewModel.onQueryUpdated(query, threadId) + context.searchBottomBar.showLoading() + context.onSearchQueryUpdated(query) + return true + } + } + searchViewItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem): Boolean { + searchView.setOnQueryTextListener(queryListener) + searchViewModel.onSearchOpened() + context.searchBottomBar.visibility = View.VISIBLE + context.searchBottomBar.setData(0, 0) + context.inputBar.visibility = View.GONE + for (i in 0 until menu.size()) { + if (menu.getItem(i) != searchViewItem) { + menu.getItem(i).isVisible = false + } + } + return true + } + + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + searchView.setOnQueryTextListener(null) + searchViewModel.onSearchClosed() + context.searchBottomBar.visibility = View.GONE + context.inputBar.visibility = View.VISIBLE + context.onSearchQueryUpdated(null) + context.invalidateOptionsMenu() + return true + } + }) } fun onOptionItemSelected(context: Context, item: MenuItem, thread: Recipient): Boolean { @@ -121,7 +164,8 @@ object ConversationMenuHelper { } private fun search(context: Context) { - Toast.makeText(context, "Not yet implemented", Toast.LENGTH_LONG).show() // TODO: Implement + val searchViewModel = (context as ConversationActivityV2).searchViewModel!! + searchViewModel.onSearchOpened() } @SuppressLint("StaticFieldLeak") @@ -254,7 +298,6 @@ object ConversationMenuHelper { try { if (isClosedGroup) { MessageSender.leave(groupPublicKey!!, true) - // TODO: Disable input? } else { Toast.makeText(context, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show() } @@ -274,13 +317,11 @@ object ConversationMenuHelper { } private fun unmute(context: Context, thread: Recipient) { - thread.setMuted(0) DatabaseFactory.getRecipientDatabase(context).setMuted(thread, 0) } private fun mute(context: Context, thread: Recipient) { MuteDialog.show(context) { until: Long -> - thread.setMuted(until) DatabaseFactory.getRecipientDatabase(context).setMuted(thread, until) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index b4f3810e16..78e926d041 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -32,6 +32,9 @@ class ControlMessageView : LinearLayout { if (message.isExpirationTimerUpdate) { iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_timer, context.theme)) iconImageView.visibility = View.VISIBLE + } else if (message.isMediaSavedNotification) { + iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_file_download_white_36dp, context.theme)) + iconImageView.visibility = View.VISIBLE } textView.text = message.getDisplayBody(context) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt index 8658dfae81..8f0e61b381 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt @@ -42,7 +42,7 @@ class LinkPreviewView : LinearLayout { // endregion // region Updating - fun bind(message: MmsMessageRecord, glide: GlideRequests, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean) { + fun bind(message: MmsMessageRecord, glide: GlideRequests, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean, searchQuery: String?) { val linkPreview = message.linkPreviews.first() url = linkPreview.url // Thumbnail @@ -60,7 +60,7 @@ class LinkPreviewView : LinearLayout { } titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme)) // Body - bodyTextView = VisibleMessageContentView.getBodyTextView(context, message) + bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery) mainLinkPreviewContainer.addView(bodyTextView) // Corner radii val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing) 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 14d62945a7..16721b1625 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 @@ -1,20 +1,20 @@ package org.thoughtcrime.securesms.conversation.v2.messages import android.content.Context +import android.graphics.Color import android.graphics.Rect import android.graphics.drawable.Drawable +import android.text.style.BackgroundColorSpan +import android.text.style.ForegroundColorSpan import android.text.method.LinkMovementMethod -import android.text.style.ReplacementSpan import android.text.style.URLSpan import android.text.util.Linkify import android.util.AttributeSet -import android.util.Log import android.util.TypedValue import android.view.LayoutInflater import android.view.MotionEvent import android.widget.LinearLayout import android.widget.TextView -import android.widget.Toast import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import androidx.appcompat.app.AppCompatActivity @@ -24,6 +24,7 @@ import androidx.core.graphics.BlendModeCompat import androidx.core.text.getSpans import androidx.core.text.toSpannable import androidx.core.text.util.LinkifyCompat +import kotlinx.android.synthetic.main.view_link_preview.view.* import kotlinx.android.synthetic.main.view_visible_message_content.view.* import network.loki.messenger.R import org.session.libsession.utilities.ThemeUtil @@ -38,11 +39,15 @@ import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.loki.utilities.* import org.thoughtcrime.securesms.mms.GlideRequests +import org.thoughtcrime.securesms.util.SearchUtil +import org.thoughtcrime.securesms.util.SearchUtil.StyleFactory +import java.util.* import kotlin.math.roundToInt class VisibleMessageContentView : LinearLayout { var onContentClick: ((event: MotionEvent) -> Unit)? = null var onContentDoubleTap: (() -> Unit)? = null + var delegate: VisibleMessageContentViewDelegate? = null // region Lifecycle constructor(context: Context) : super(context) { initialize() } @@ -56,7 +61,7 @@ class VisibleMessageContentView : LinearLayout { // region Updating fun bind(message: MessageRecord, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean, - glide: GlideRequests, maxWidth: Int, thread: Recipient) { + glide: GlideRequests, maxWidth: Int, thread: Recipient, searchQuery: String?) { // Background val background = getBackground(message.isOutgoing, isStartOfMessageCluster, isEndOfMessageCluster) val colorID = if (message.isOutgoing) R.attr.message_sent_background_color else R.attr.message_received_background_color @@ -70,7 +75,7 @@ class VisibleMessageContentView : LinearLayout { onContentDoubleTap = null if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) { val linkPreviewView = LinkPreviewView(context) - linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster) + linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster, searchQuery) mainContainer.addView(linkPreviewView) onContentClick = { event -> linkPreviewView.calculateHit(event) } // Body text view is inside the link preview for layout convenience @@ -84,9 +89,16 @@ class VisibleMessageContentView : LinearLayout { quoteView.bind(quote.author.toString(), quote.text, quote.attachment, thread, message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation, message.threadId, glide) mainContainer.addView(quoteView) - val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message) + val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery) ViewUtil.setPaddingTop(bodyTextView, 0) mainContainer.addView(bodyTextView) + onContentClick = { event -> + val r = Rect() + quoteView.getGlobalVisibleRect(r) + if (r.contains(event.rawX.roundToInt(), event.rawY.roundToInt())) { + delegate?.scrollToMessageIfPossible(quote.id) + } + } } else if (message is MmsMessageRecord && message.slideDeck.audioSlide != null) { val voiceMessageView = VoiceMessageView(context) voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster) @@ -119,7 +131,7 @@ class VisibleMessageContentView : LinearLayout { mainContainer.addView(openGroupInvitationView) onContentClick = { openGroupInvitationView.joinOpenGroup() } } else { - val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message) + val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery) mainContainer.addView(bodyTextView) onContentClick = { event -> // intersectedModalSpans should only be a list of one item @@ -153,7 +165,7 @@ class VisibleMessageContentView : LinearLayout { // region Convenience companion object { - fun getBodyTextView(context: Context, message: MessageRecord): TextView { + fun getBodyTextView(context: Context, message: MessageRecord, searchQuery: String?): TextView { val result = EmojiTextView(context) val vPadding = context.resources.getDimension(R.dimen.small_spacing).toInt() val hPadding = toPx(12, context.resources) @@ -177,8 +189,11 @@ class VisibleMessageContentView : LinearLayout { body.removeSpan(urlSpan) body.setSpan(replacementSpan, start, end, flags) } - + body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context) + body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { BackgroundColorSpan(Color.WHITE) }, body, searchQuery) + body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { ForegroundColorSpan(Color.BLACK) }, body, searchQuery) + result.text = body return result } @@ -195,4 +210,9 @@ class VisibleMessageContentView : LinearLayout { } } // endregion +} + +interface VisibleMessageContentViewDelegate { + + fun scrollToMessageIfPossible(timestamp: Long) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 356a9e2fbb..93f0236f36 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -3,21 +3,27 @@ package org.thoughtcrime.securesms.conversation.v2.messages import android.content.Context import android.content.res.Resources import android.graphics.Canvas +import android.graphics.ColorFilter import android.graphics.Rect import android.graphics.drawable.ColorDrawable +import android.os.AsyncTask import android.os.Build import android.os.Handler import android.os.Looper import android.util.AttributeSet import android.view.* import android.widget.LinearLayout +import android.widget.RelativeLayout import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible import kotlinx.android.synthetic.main.view_visible_message.view.* import network.loki.messenger.R import org.session.libsession.messaging.contacts.Contact.ContactContext import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.utilities.ViewUtil +import org.session.libsignal.utilities.ThreadUtils +import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.loki.utilities.getColorWithID @@ -47,6 +53,7 @@ class VisibleMessageView : LinearLayout { var onPress: ((event: MotionEvent) -> Unit)? = null var onSwipeToReply: (() -> Unit)? = null var onLongPress: (() -> Unit)? = null + var contentViewDelegate: VisibleMessageContentViewDelegate? = null companion object { const val swipeToReplyThreshold = 80.0f // dp @@ -69,7 +76,7 @@ class VisibleMessageView : LinearLayout { // endregion // region Updating - fun bind(message: MessageRecord, previous: MessageRecord?, next: MessageRecord?, glide: GlideRequests) { + fun bind(message: MessageRecord, previous: MessageRecord?, next: MessageRecord?, glide: GlideRequests, searchQuery: String?) { val sender = message.individualRecipient val senderSessionID = sender.address.serialize() val threadID = message.threadId @@ -137,11 +144,14 @@ class VisibleMessageView : LinearLayout { } else { messageStatusImageView.isVisible = false } + // Expiration timer + updateExpirationTimer(message) // Calculate max message bubble width var maxWidth = screenWidth - messageContentContainerLayoutParams.leftMargin - messageContentContainerLayoutParams.rightMargin if (profilePictureContainer.visibility != View.GONE) { maxWidth -= profilePictureContainer.width } // Populate content view - messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread) + messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread, searchQuery) + messageContentView.delegate = contentViewDelegate onDoubleTap = { messageContentView.onContentDoubleTap?.invoke() } } @@ -182,6 +192,37 @@ class VisibleMessageView : LinearLayout { } } + private fun updateExpirationTimer(message: MessageRecord) { + val expirationTimerViewLayoutParams = expirationTimerView.layoutParams as RelativeLayout.LayoutParams + val ruleToAdd = if (message.isOutgoing) RelativeLayout.ALIGN_PARENT_START else RelativeLayout.ALIGN_PARENT_END + val ruleToRemove = if (message.isOutgoing) RelativeLayout.ALIGN_PARENT_END else RelativeLayout.ALIGN_PARENT_START + expirationTimerViewLayoutParams.removeRule(ruleToRemove) + expirationTimerViewLayoutParams.addRule(ruleToAdd) + expirationTimerView.layoutParams = expirationTimerViewLayoutParams + if (message.expiresIn > 0 && !message.isPending) { + expirationTimerView.setColorFilter(ResourcesCompat.getColor(resources, R.color.text, context.theme)) + expirationTimerView.isVisible = true + expirationTimerView.setPercentComplete(0.0f) + if (message.expireStarted > 0) { + expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn) + expirationTimerView.startAnimation() + if (message.expireStarted + message.expiresIn <= System.currentTimeMillis()) { + ApplicationContext.getInstance(context).expiringMessageManager.checkSchedule() + } + } else if (!message.isOutgoing && !message.isMediaPending) { + ThreadUtils.queue { + val expirationManager = ApplicationContext.getInstance(context).expiringMessageManager + val id = message.getId() + val mms = message.isMms + if (mms) DatabaseFactory.getMmsDatabase(context).markExpireStarted(id) else DatabaseFactory.getSmsDatabase(context).markExpireStarted(id) + expirationManager.scheduleDeletion(id, mms, message.expiresIn) + } + } + } else { + expirationTimerView.isVisible = false + } + } + private fun handleIsSelectedChanged() { background = if (snIsSelected) { ColorDrawable(context.resources.getColorWithID(R.color.message_selected, context.theme)) @@ -242,6 +283,7 @@ class VisibleMessageView : LinearLayout { } else { longPressCallback?.let { gestureHandler.removeCallbacks(it) } } + if (translationX > 0) { return } // Only allow swipes to the left // The idea here is to asymptotically approach a maximum drag distance val damping = 50.0f val sign = -1.0f diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchBottomBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchBottomBar.kt new file mode 100644 index 0000000000..da8df0045a --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchBottomBar.kt @@ -0,0 +1,62 @@ +package org.thoughtcrime.securesms.conversation.v2.search + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.LinearLayout +import kotlinx.android.synthetic.main.view_search_bottom_bar.view.* +import network.loki.messenger.R + + +class SearchBottomBar : LinearLayout { + private var eventListener: EventListener? = null + + // region Lifecycle + constructor(context: Context) : super(context) { initialize() } + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } + + fun initialize() { + LayoutInflater.from(context).inflate(R.layout.view_search_bottom_bar, this) + } + + fun setData(position: Int, count: Int) { + searchProgressWheel.visibility = GONE + searchUp.setOnClickListener { v: View? -> + if (eventListener != null) { + eventListener!!.onSearchMoveUpPressed() + } + } + searchDown.setOnClickListener { v: View? -> + if (eventListener != null) { + eventListener!!.onSearchMoveDownPressed() + } + } + if (count > 0) { + searchPosition.text = resources.getString(R.string.ConversationActivity_search_position, position + 1, count) + } else { + searchPosition.text = "" + } + setViewEnabled(searchUp, position < count - 1) + setViewEnabled(searchDown, position > 0) + } + + fun showLoading() { + searchProgressWheel.visibility = VISIBLE + } + + private fun setViewEnabled(view: View, enabled: Boolean) { + view.isEnabled = enabled + view.alpha = if (enabled) 1f else 0.25f + } + + fun setEventListener(eventListener: EventListener?) { + this.eventListener = eventListener + } + + interface EventListener { + fun onSearchMoveUpPressed() + fun onSearchMoveDownPressed() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchViewModel.kt new file mode 100644 index 0000000000..eb3dd50d98 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/search/SearchViewModel.kt @@ -0,0 +1,111 @@ +package org.thoughtcrime.securesms.conversation.v2.search + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import org.session.libsession.utilities.Debouncer +import org.session.libsession.utilities.Util.runOnMain +import org.session.libsession.utilities.concurrent.SignalExecutors +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 java.io.Closeable + + +class SearchViewModel(application: Application) : AndroidViewModel(application) { + private val searchRepository: SearchRepository + private val result: CloseableLiveData + private val debouncer: Debouncer + private var firstSearch = false + private var searchOpen = false + private var activeQuery: String? = null + private var activeThreadId: Long = 0 + val searchResults: LiveData + get() = result + + fun onQueryUpdated(query: String, threadId: Long) { + if (query == activeQuery) { + return + } + updateQuery(query, threadId) + } + + fun onMissingResult() { + if (activeQuery != null) { + updateQuery(activeQuery!!, activeThreadId) + } + } + + fun onMoveUp() { + debouncer.clear() + val messages = result.value!!.getResults() as CursorList + val position = Math.min(result.value!!.position + 1, messages.size - 1) + result.setValue(SearchResult(messages, position), false) + } + + fun onMoveDown() { + debouncer.clear() + val messages = result.value!!.getResults() as CursorList + val position = Math.max(result.value!!.position - 1, 0) + result.setValue(SearchResult(messages, position), false) + } + + fun onSearchOpened() { + searchOpen = true + firstSearch = true + } + + fun onSearchClosed() { + searchOpen = false + activeQuery = null + debouncer.clear() + result.close() + } + + override fun onCleared() { + super.onCleared() + result.close() + } + + private fun updateQuery(query: String, threadId: Long) { + activeQuery = query + activeThreadId = threadId + debouncer.publish { + firstSearch = false + searchRepository.query(query, threadId) { messages: CursorList -> + runOnMain { + if (searchOpen && query == activeQuery) { + result.setValue(SearchResult(messages, 0)) + } else { + messages.close() + } + } + } + } + } + + class SearchResult(private val results: CursorList, val position: Int) : Closeable { + + fun getResults(): List { + return results + } + + override fun close() { + results.close() + } + } + + init { + val context = application.applicationContext + result = CloseableLiveData() + debouncer = Debouncer(500) + searchRepository = SearchRepository(context, + DatabaseFactory.getSearchDatabase(context), + DatabaseFactory.getThreadDatabase(context), + ContactAccessor.getInstance(), + SignalExecutors.SERIAL) + } +} \ No newline at end of file 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 ed443591f0..ce2ddba24a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -57,6 +57,7 @@ import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientFormattingException; import org.session.libsignal.utilities.JsonUtil; import org.session.libsignal.utilities.Log; +import org.session.libsignal.utilities.ThreadUtils; import org.session.libsignal.utilities.guava.Optional; import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; @@ -881,9 +882,9 @@ public class MmsDatabase extends MessagingDatabase { } public boolean delete(long messageId) { - long threadId = getThreadIdForMessage(messageId); + long threadId = getThreadIdForMessage(messageId); AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context); - attachmentDatabase.deleteAttachmentsForMessage(messageId); + ThreadUtils.queue(() -> attachmentDatabase.deleteAttachmentsForMessage(messageId)); GroupReceiptDatabase groupReceiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); groupReceiptDatabase.deleteRowsForMessage(messageId); 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 d241db9862..6706f5fe77 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -20,6 +20,8 @@ package org.thoughtcrime.securesms.database; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.os.Handler; +import android.os.Looper; import android.text.TextUtils; import android.util.Pair; @@ -411,7 +413,6 @@ public class SmsDatabase extends MessagingDatabase { notifyConversationListeners(threadId); - return Optional.of(new InsertResult(messageId, threadId)); } } @@ -512,7 +513,7 @@ public class SmsDatabase extends MessagingDatabase { public boolean deleteMessage(long messageId) { Log.i("MessageDatabase", "Deleting: " + messageId); SQLiteDatabase db = databaseHelper.getWritableDatabase(); - long threadId = getThreadIdForMessage(messageId); + long threadId = getThreadIdForMessage(messageId); db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""}); boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false); notifyConversationListeners(threadId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 75db53493b..15b91048e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.database -import android.app.job.JobScheduler import android.content.Context import android.net.Uri import org.session.libsession.database.StorageProtocol @@ -106,7 +105,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } else -> Optional.absent() } - val pointerAttachments = attachments.mapNotNull { + val pointers = attachments.mapNotNull { it.toSignalAttachment() } val targetAddress = if (isUserSender && !message.syncTarget.isNullOrEmpty()) { @@ -122,7 +121,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val linkPreviews: Optional> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! }) val mmsDatabase = DatabaseFactory.getMmsDatabase(context) val insertResult = if (message.sender == getUserPublicKey()) { - val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointerAttachments, quote.orNull(), linkPreviews.orNull()?.firstOrNull()) + val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointers, quote.orNull(), linkPreviews.orNull()?.firstOrNull()) mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) } else { // It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment 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 f13dee1d46..df4f3ce614 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 @@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.loki.utilities.fadeOut import org.thoughtcrime.securesms.mms.GlideApp import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.TextSecurePreferences +import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 //TODO Refactor to avoid using kotlinx.android.synthetic class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks> { @@ -135,10 +136,10 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM // region Convenience private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) { - val intent = Intent(context, ConversationActivity::class.java) - intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId) + val intent = Intent(context, ConversationActivityV2::class.java) + intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT) - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address) + intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) context.startActivity(intent) } // endregion \ No newline at end of file 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 7f75b9cc77..7d429bf223 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 @@ -30,6 +30,7 @@ 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 import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate @@ -111,12 +112,12 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC private fun createPrivateChat(hexEncodedPublicKey: String) { val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false) - val intent = Intent(this, ConversationActivity::class.java) - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address) + 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(ConversationActivity.THREAD_ID_EXTRA, existingThread) + 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/JoinPublicChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt index deec2bf434..dc038cc0cb 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 @@ -33,6 +33,7 @@ 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 import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment @@ -127,10 +128,10 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode // region Convenience private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) { - val intent = Intent(context, ConversationActivity::class.java) - intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId) + val intent = Intent(context, ConversationActivityV2::class.java) + intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT) - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address) + intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) context.startActivity(intent) } // endregion 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 dd6bf3420e..96d175ef99 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 @@ -26,6 +26,7 @@ import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.util.FileProviderUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.PublicKeyValidation +import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import java.io.File import java.io.FileOutputStream @@ -53,12 +54,12 @@ class QRCodeActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperF fun createPrivateChatIfPossible(hexEncodedPublicKey: String) { if (!PublicKeyValidation.isValid(hexEncodedPublicKey)) { return Toast.makeText(this, R.string.invalid_session_id, Toast.LENGTH_SHORT).show() } val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false) - val intent = Intent(this, ConversationActivity::class.java) - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address) + 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(ConversationActivity.THREAD_ID_EXTRA, existingThread) + 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/views/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt index 3482088dd9..b07675f5ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt @@ -31,23 +31,12 @@ class ProfilePictureView : RelativeLayout { private val profilePicturesCache = mutableMapOf() // region Lifecycle - constructor(context: Context) : super(context) { - setUpViewHierarchy() - } + constructor(context: Context) : super(context) { initialize() } + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { initialize() } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - setUpViewHierarchy() - } - - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { - setUpViewHierarchy() - } - - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { - setUpViewHierarchy() - } - - private fun setUpViewHierarchy() { + private fun initialize() { val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater val contentView = inflater.inflate(R.layout.view_profile_picture, null) addView(contentView) 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 532c953152..a0c1046e07 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -49,6 +49,7 @@ 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; import org.thoughtcrime.securesms.database.MmsSmsDatabase; @@ -115,9 +116,9 @@ public class DefaultMessageNotifier implements MessageNotifier { if (visibleThread == threadId) { sendInThreadNotification(context, recipient); } else { - Intent intent = new Intent(context, ConversationActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress()); - intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); + Intent intent = new Intent(context, ConversationActivityV2.class); + intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress()); + intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); intent.setData((Uri.parse("custom://" + System.currentTimeMillis()))); FailedNotificationBuilder builder = new FailedNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context), intent); 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 e92f62aa44..c2041a84fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java @@ -9,6 +9,7 @@ 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; @@ -67,11 +68,11 @@ public class NotificationItem { } public PendingIntent getPendingIntent(Context context) { - Intent intent = new Intent(context, ConversationActivity.class); + Intent intent = new Intent(context, ConversationActivityV2.class); Recipient notifyRecipients = threadRecipient != null ? threadRecipient : conversationRecipient; - if (notifyRecipients != null) intent.putExtra(ConversationActivity.ADDRESS_EXTRA, notifyRecipients.getAddress()); + if (notifyRecipients != null) intent.putExtra(ConversationActivityV2.ADDRESS, notifyRecipients.getAddress()); - intent.putExtra("thread_id", threadId); + intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); return TaskStackBuilder.create(context) 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 418de713cb..e8dd7cd589 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -14,6 +14,7 @@ import android.widget.Toast; import org.thoughtcrime.securesms.conversation.ConversationActivity; import network.loki.messenger.R; +import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.session.libsession.utilities.recipients.Recipient; @@ -32,9 +33,9 @@ public class CommunicationActions { @Override protected void onPostExecute(Long threadId) { - Intent intent = new Intent(context, ConversationActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress()); - intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId); + 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)) { diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index 66d71f5cce..079c932e99 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -34,6 +34,13 @@ android:layout_height="wrap_content" android:layout_alignParentBottom="true" /> + + - + android:layout_width="@dimen/medium_profile_picture_size" + android:layout_height="@dimen/medium_profile_picture_size" /> - + android:layout_marginBottom="@dimen/small_spacing" + app:tint="@color/text" /> + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_visible_message.xml b/app/src/main/res/layout/view_visible_message.xml index 7148d07a05..b3d258c8ea 100644 --- a/app/src/main/res/layout/view_visible_message.xml +++ b/app/src/main/res/layout/view_visible_message.xml @@ -1,114 +1,126 @@ - - - + android:layout_height="wrap_content"> + android:orientation="vertical"> + + + android:orientation="horizontal" + android:gravity="bottom"> - - - - - - - - - - - - - - - - - + android:orientation="horizontal"> - + + + + + + + + + + + + + + - - + android:layout_height="wrap_content" + android:orientation="vertical"> + android:ellipsize="end" /> - + - + + + + + + + + + - \ No newline at end of file + + + \ No newline at end of file