mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-25 09:17:44 +00:00
Merge remote-tracking branch 'upstream/ui' into ui
# Conflicts: # app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt
This commit is contained in:
commit
7459765a52
@ -143,7 +143,7 @@ dependencies {
|
|||||||
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 184
|
def canonicalVersionCode = 186
|
||||||
def canonicalVersionName = "1.11.0"
|
def canonicalVersionName = "1.11.0"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 10
|
||||||
|
@ -39,6 +39,7 @@ import org.session.libsession.utilities.DistributionTypes;
|
|||||||
import org.thoughtcrime.securesms.components.SearchToolbar;
|
import org.thoughtcrime.securesms.components.SearchToolbar;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
||||||
import org.session.libsession.utilities.Address;
|
import org.session.libsession.utilities.Address;
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
import org.thoughtcrime.securesms.loki.fragments.ContactSelectionListFragment;
|
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) {
|
private void createConversation(long threadId, Address address, int distributionType) {
|
||||||
final Intent intent = getBaseShareIntent(ConversationActivity.class);
|
final Intent intent = getBaseShareIntent(ConversationActivityV2.class);
|
||||||
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, address);
|
intent.putExtra(ConversationActivityV2.ADDRESS, address);
|
||||||
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
|
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
|
||||||
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType);
|
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType);
|
||||||
|
|
||||||
isPassingAlongMedia = true;
|
isPassingAlongMedia = true;
|
||||||
|
@ -14,6 +14,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
|
||||||
|
@ -14,6 +14,7 @@ import android.view.WindowManager;
|
|||||||
|
|
||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
import org.session.libsignal.utilities.ListenableFuture;
|
import org.session.libsignal.utilities.ListenableFuture;
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
@ -80,9 +81,9 @@ public class ConversationPopupActivity extends ConversationActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Long result) {
|
public void onSuccess(Long result) {
|
||||||
ActivityOptionsCompat transition = ActivityOptionsCompat.makeScaleUpAnimation(getWindow().getDecorView(), 0, 0, getWindow().getAttributes().width, getWindow().getAttributes().height);
|
ActivityOptionsCompat transition = ActivityOptionsCompat.makeScaleUpAnimation(getWindow().getDecorView(), 0, 0, getWindow().getAttributes().width, getWindow().getAttributes().height);
|
||||||
Intent intent = new Intent(ConversationPopupActivity.this, ConversationActivity.class);
|
Intent intent = new Intent(ConversationPopupActivity.this, ConversationActivityV2.class);
|
||||||
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, getRecipient().getAddress());
|
intent.putExtra(ConversationActivityV2.ADDRESS, getRecipient().getAddress());
|
||||||
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, result);
|
intent.putExtra(ConversationActivityV2.THREAD_ID, result);
|
||||||
|
|
||||||
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
|
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
|
||||||
startActivity(intent, transition.toBundle());
|
startActivity(intent, transition.toBundle());
|
||||||
|
@ -18,10 +18,14 @@ import android.util.Log
|
|||||||
import android.util.Pair
|
import android.util.Pair
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
import android.widget.LinearLayout
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.DimenRes
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import androidx.loader.app.LoaderManager
|
import androidx.loader.app.LoaderManager
|
||||||
import androidx.loader.content.Loader
|
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.sending_receiving.quotes.QuoteModel
|
||||||
import org.session.libsession.messaging.utilities.UpdateMessageData
|
import org.session.libsession.messaging.utilities.UpdateMessageData
|
||||||
import org.session.libsession.messaging.utilities.UpdateMessageData.Companion.fromJSON
|
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.Address.Companion.fromSerialized
|
||||||
import org.session.libsession.utilities.MediaTypes
|
import org.session.libsession.utilities.MediaTypes
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
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.Recipient
|
||||||
|
import org.session.libsession.utilities.recipients.RecipientModifiedListener
|
||||||
import org.session.libsignal.utilities.ListenableFuture
|
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.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.audio.AudioRecorder
|
import org.thoughtcrime.securesms.audio.AudioRecorder
|
||||||
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher
|
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.dialogs.*
|
||||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton
|
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton
|
||||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate
|
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.ConversationActionModeCallback
|
||||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallbackDelegate
|
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallbackDelegate
|
||||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper
|
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.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.conversation.v2.utilities.AttachmentManager
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.DraftDatabase
|
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.
|
// price we pay is a bit of back and forth between the input bar and the conversation activity.
|
||||||
|
|
||||||
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate,
|
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate,
|
||||||
InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher,
|
InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher,
|
||||||
ConversationActionModeCallbackDelegate {
|
ConversationActionModeCallbackDelegate, VisibleMessageContentViewDelegate, RecipientModifiedListener,
|
||||||
|
SearchBottomBar.EventListener {
|
||||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||||
private var linkPreviewViewModel: LinkPreviewViewModel? = null
|
private var linkPreviewViewModel: LinkPreviewViewModel? = null
|
||||||
private var threadID: Long = -1
|
private var threadID: Long = -1
|
||||||
@ -130,6 +143,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
private var previousText: CharSequence = ""
|
private var previousText: CharSequence = ""
|
||||||
private var currentMentionStartIndex = -1
|
private var currentMentionStartIndex = -1
|
||||||
private var isShowingMentionCandidatesView = false
|
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
|
private val layoutManager: LinearLayoutManager
|
||||||
get() { return conversationRecyclerView.layoutManager as LinearLayoutManager }
|
get() { return conversationRecyclerView.layoutManager as LinearLayoutManager }
|
||||||
@ -150,6 +172,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
},
|
},
|
||||||
glide
|
glide
|
||||||
)
|
)
|
||||||
|
adapter.visibleMessageContentViewDelegate = this
|
||||||
adapter
|
adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +189,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
|
|
||||||
// region Settings
|
// region Settings
|
||||||
companion object {
|
companion object {
|
||||||
|
// Extras
|
||||||
const val THREAD_ID = "thread_id"
|
const val THREAD_ID = "thread_id"
|
||||||
|
const val ADDRESS = "address"
|
||||||
|
// Request codes
|
||||||
const val PICK_DOCUMENT = 2
|
const val PICK_DOCUMENT = 2
|
||||||
const val TAKE_PHOTO = 7
|
const val TAKE_PHOTO = 7
|
||||||
const val PICK_GIF = 10
|
const val PICK_GIF = 10
|
||||||
@ -179,7 +205,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
||||||
super.onCreate(savedInstanceState, isReady)
|
super.onCreate(savedInstanceState, isReady)
|
||||||
setContentView(R.layout.activity_conversation_v2)
|
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>(ADDRESS) ?: return finish()
|
||||||
|
val recipient = Recipient.from(this, address, false)
|
||||||
|
threadID = DatabaseFactory.getThreadDatabase(this).getOrCreateThreadIdFor(recipient)
|
||||||
|
}
|
||||||
|
this.threadID = threadID
|
||||||
setUpRecyclerView()
|
setUpRecyclerView()
|
||||||
setUpToolBar()
|
setUpToolBar()
|
||||||
setUpInputBar()
|
setUpInputBar()
|
||||||
@ -189,12 +221,16 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
unreadCount = DatabaseFactory.getMmsSmsDatabase(this).getUnreadCount(threadID)
|
unreadCount = DatabaseFactory.getMmsSmsDatabase(this).getUnreadCount(threadID)
|
||||||
updateUnreadCountIndicator()
|
updateUnreadCountIndicator()
|
||||||
setUpTypingObserver()
|
setUpTypingObserver()
|
||||||
|
setUpRecipientObserver()
|
||||||
updateSubtitle()
|
updateSubtitle()
|
||||||
getLatestOpenGroupInfoIfNeeded()
|
getLatestOpenGroupInfoIfNeeded()
|
||||||
setUpBlockedBanner()
|
setUpBlockedBanner()
|
||||||
setUpLinkPreviewObserver()
|
setUpLinkPreviewObserver()
|
||||||
|
searchBottomBar.setEventListener(this)
|
||||||
|
setUpSearchResultObserver()
|
||||||
scrollToFirstUnreadMessageIfNeeded()
|
scrollToFirstUnreadMessageIfNeeded()
|
||||||
markAllAsRead()
|
markAllAsRead()
|
||||||
|
showOrHideInputIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@ -251,6 +287,14 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
actionBar.setCustomView(R.layout.activity_conversation_v2_action_bar)
|
actionBar.setCustomView(R.layout.activity_conversation_v2_action_bar)
|
||||||
actionBar.setDisplayShowCustomEnabled(true)
|
actionBar.setDisplayShowCustomEnabled(true)
|
||||||
conversationTitleView.text = thread.toShortString()
|
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.glide = glide
|
||||||
profilePictureView.update(thread, threadID)
|
profilePictureView.update(thread, threadID)
|
||||||
}
|
}
|
||||||
@ -281,6 +325,27 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun restoreDraftIfNeeded() {
|
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<Boolean> {
|
||||||
|
|
||||||
|
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 draftDB = DatabaseFactory.getDraftDatabase(this)
|
||||||
val drafts = draftDB.getDrafts(threadID)
|
val drafts = draftDB.getDrafts(threadID)
|
||||||
draftDB.clearDrafts(threadID)
|
draftDB.clearDrafts(threadID)
|
||||||
@ -302,7 +367,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
private fun setUpTypingObserver() {
|
private fun setUpTypingObserver() {
|
||||||
ApplicationContext.getInstance(this).typingStatusRepository.getTypists(threadID).observe(this) { state ->
|
ApplicationContext.getInstance(this).typingStatusRepository.getTypists(threadID).observe(this) { state ->
|
||||||
val recipients = if (state != null) state.typists else listOf()
|
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)
|
typingIndicatorViewContainer.setTypists(recipients)
|
||||||
inputBarHeightChanged(inputBar.height)
|
inputBarHeightChanged(inputBar.height)
|
||||||
}
|
}
|
||||||
@ -316,6 +383,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setUpRecipientObserver() {
|
||||||
|
thread.addListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getLatestOpenGroupInfoIfNeeded() {
|
private fun getLatestOpenGroupInfoIfNeeded() {
|
||||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID) ?: return
|
val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID) ?: return
|
||||||
OpenGroupAPIV2.getMemberCount(openGroup.room, openGroup.server).successUi { updateSubtitle() }
|
OpenGroupAPIV2.getMemberCount(openGroup.room, openGroup.server).successUi { updateSubtitle() }
|
||||||
@ -358,7 +429,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
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)
|
super.onPrepareOptionsMenu(menu)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -369,7 +440,28 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
// endregion
|
// 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() {
|
private fun markAllAsRead() {
|
||||||
val messages = DatabaseFactory.getThreadDatabase(this).setRead(threadID, true)
|
val messages = DatabaseFactory.getThreadDatabase(this).setRead(threadID, true)
|
||||||
if (thread.isGroupRecipient) {
|
if (thread.isGroupRecipient) {
|
||||||
@ -570,8 +662,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRecyclerViewScrolled() {
|
private fun handleRecyclerViewScrolled() {
|
||||||
val position = layoutManager.findFirstCompletelyVisibleItemPosition()
|
val alpha = if (!isScrolledToBottom) 1.0f else 0.0f
|
||||||
val alpha = if (position > 0) 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
|
scrollToBottomButton.alpha = alpha
|
||||||
unreadCount = min(unreadCount, layoutManager.findFirstVisibleItemPosition())
|
unreadCount = min(unreadCount, layoutManager.findFirstVisibleItemPosition())
|
||||||
updateUnreadCountIndicator()
|
updateUnreadCountIndicator()
|
||||||
@ -644,6 +743,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
val actionMode = this.actionMode
|
val actionMode = this.actionMode
|
||||||
val actionModeCallback = ConversationActionModeCallback(adapter, threadID, this)
|
val actionModeCallback = ConversationActionModeCallback(adapter, threadID, this)
|
||||||
actionModeCallback.delegate = this
|
actionModeCallback.delegate = this
|
||||||
|
searchViewItem?.collapseActionView()
|
||||||
if (actionMode == null) { // Nothing should be selected if this is the case
|
if (actionMode == null) { // Nothing should be selected if this is the case
|
||||||
adapter.toggleSelection(message, position)
|
adapter.toggleSelection(message, position)
|
||||||
this.actionMode = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
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
|
this.previousText = newText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun scrollToMessageIfPossible(timestamp: Long) {
|
||||||
|
val lastSeenItemPosition = adapter.getItemPositionForTimestamp(timestamp) ?: return
|
||||||
|
conversationRecyclerView.scrollToPosition(lastSeenItemPosition)
|
||||||
|
}
|
||||||
|
|
||||||
override fun sendMessage() {
|
override fun sendMessage() {
|
||||||
if (thread.isContactRecipient && thread.isBlocked) {
|
if (thread.isContactRecipient && thread.isBlocked) {
|
||||||
BlockedDialog(thread).show(supportFragmentManager, "Blocked Dialog")
|
BlockedDialog(thread).show(supportFragmentManager, "Blocked Dialog")
|
||||||
@ -908,10 +1013,18 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun startRecordingVoiceMessage() {
|
override fun startRecordingVoiceMessage() {
|
||||||
showVoiceMessageUI()
|
if (Permissions.hasAll(this, Manifest.permission.RECORD_AUDIO)) {
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
showVoiceMessageUI()
|
||||||
audioRecorder.startRecording()
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
stopAudioHandler.postDelayed(stopVoiceMessageRecordingTask, 60000) // Limit voice messages to 1 minute each
|
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() {
|
override fun sendVoiceMessage() {
|
||||||
@ -966,13 +1079,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ThreadUtils.queue {
|
for (message in messages) {
|
||||||
for (message in messages) {
|
if (message.isMms) {
|
||||||
if (message.isMms) {
|
DatabaseFactory.getMmsDatabase(this@ConversationActivityV2).delete(message.id)
|
||||||
DatabaseFactory.getMmsDatabase(this@ConversationActivityV2).delete(message.id)
|
} else {
|
||||||
} else {
|
DatabaseFactory.getSmsDatabase(this@ConversationActivityV2).deleteMessage(message.id)
|
||||||
DatabaseFactory.getSmsDatabase(this@ConversationActivityV2).deleteMessage(message.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1157,4 +1268,46 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
draftDB.insertDrafts(threadID, drafts)
|
draftDB.insertDrafts(threadID, drafts)
|
||||||
}
|
}
|
||||||
// endregion
|
// 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
|
||||||
}
|
}
|
@ -9,6 +9,7 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
import kotlinx.android.synthetic.main.view_visible_message.view.*
|
import kotlinx.android.synthetic.main.view_visible_message.view.*
|
||||||
import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
|
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.conversation.v2.messages.VisibleMessageView
|
||||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
@ -21,6 +22,8 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr
|
|||||||
: CursorRecyclerViewAdapter<ViewHolder>(context, cursor) {
|
: CursorRecyclerViewAdapter<ViewHolder>(context, cursor) {
|
||||||
private val messageDB = DatabaseFactory.getMmsSmsDatabase(context)
|
private val messageDB = DatabaseFactory.getMmsSmsDatabase(context)
|
||||||
var selectedItems = mutableSetOf<MessageRecord>()
|
var selectedItems = mutableSetOf<MessageRecord>()
|
||||||
|
private var searchQuery: String? = null
|
||||||
|
var visibleMessageContentViewDelegate: VisibleMessageContentViewDelegate? = null
|
||||||
|
|
||||||
sealed class ViewType(val rawValue: Int) {
|
sealed class ViewType(val rawValue: Int) {
|
||||||
object Visible : ViewType(0)
|
object Visible : ViewType(0)
|
||||||
@ -69,10 +72,11 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr
|
|||||||
view.snIsSelected = isSelected
|
view.snIsSelected = isSelected
|
||||||
view.messageTimestampTextView.isVisible = isSelected
|
view.messageTimestampTextView.isVisible = isSelected
|
||||||
val position = viewHolder.adapterPosition
|
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.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, view, event) }
|
||||||
view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) }
|
view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) }
|
||||||
view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) }
|
view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) }
|
||||||
|
view.contentViewDelegate = visibleMessageContentViewDelegate
|
||||||
}
|
}
|
||||||
is ControlMessageViewHolder -> viewHolder.view.bind(message)
|
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
|
if (lastSeenTimestamp <= 0L || cursor == null || !isActiveCursor) return null
|
||||||
for (i in 0 until itemCount) {
|
for (i in 0 until itemCount) {
|
||||||
cursor.moveToPosition(i)
|
cursor.moveToPosition(i)
|
||||||
val messageRecord = messageDB.readerFor(cursor).current
|
val message = messageDB.readerFor(cursor).current
|
||||||
if (messageRecord.isOutgoing || messageRecord.dateReceived <= lastSeenTimestamp) { return i }
|
if (message.isOutgoing || message.dateReceived <= lastSeenTimestamp) { return i }
|
||||||
}
|
}
|
||||||
return null
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package org.thoughtcrime.securesms.components;
|
package org.thoughtcrime.securesms.conversation.v2.components;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.NonNull;
|
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));
|
Util.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -6,10 +6,12 @@ import android.text.SpannableStringBuilder
|
|||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import kotlinx.android.synthetic.main.dialog_join_open_group.view.*
|
import kotlinx.android.synthetic.main.dialog_join_open_group.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
||||||
import org.session.libsession.utilities.OpenGroupUrlParser
|
import org.session.libsession.utilities.OpenGroupUrlParser
|
||||||
|
import org.session.libsignal.utilities.ThreadUtils
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
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() {
|
private fun join() {
|
||||||
val openGroup = OpenGroupUrlParser.parseUrl(url)
|
val openGroup = OpenGroupUrlParser.parseUrl(url)
|
||||||
OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, requireContext())
|
val activity = requireContext() as AppCompatActivity
|
||||||
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(requireContext())
|
ThreadUtils.queue {
|
||||||
|
OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, activity)
|
||||||
|
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(activity)
|
||||||
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -32,6 +32,8 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
|
|||||||
var additionalContentHeight = 0
|
var additionalContentHeight = 0
|
||||||
var quote: MessageRecord? = null
|
var quote: MessageRecord? = null
|
||||||
var linkPreview: LinkPreview? = null
|
var linkPreview: LinkPreview? = null
|
||||||
|
var showInput: Boolean = true
|
||||||
|
set(value) { field = value; showOrHideInputIfNeeded() }
|
||||||
|
|
||||||
var text: String
|
var text: String
|
||||||
get() { return inputBarEditText.text.toString() }
|
get() { return inputBarEditText.text.toString() }
|
||||||
@ -161,6 +163,19 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
|
|||||||
additionalContentHeight = 0
|
additionalContentHeight = 0
|
||||||
setHeight(newHeight)
|
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
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,17 +12,20 @@ import android.os.AsyncTask
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
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.ShortcutInfoCompat
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
import kotlinx.android.synthetic.main.activity_conversation_v2.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.avatars.ContactPhoto
|
|
||||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
import org.session.libsession.messaging.sending_receiving.leave
|
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.GroupUtil.doubleDecodeGroupID
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
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.guava.Optional
|
||||||
import org.session.libsignal.utilities.toHexString
|
import org.session.libsignal.utilities.toHexString
|
||||||
import org.thoughtcrime.securesms.*
|
import org.thoughtcrime.securesms.*
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity
|
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.loki.utilities.getColorWithID
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
|
|
||||||
object ConversationMenuHelper {
|
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
|
// Prepare
|
||||||
menu.clear()
|
menu.clear()
|
||||||
val isOpenGroup = thread.isOpenGroupRecipient
|
val isOpenGroup = thread.isOpenGroupRecipient
|
||||||
@ -92,6 +92,49 @@ object ConversationMenuHelper {
|
|||||||
} else {
|
} else {
|
||||||
inflater.inflate(R.menu.menu_conversation_unmuted, menu)
|
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 {
|
fun onOptionItemSelected(context: Context, item: MenuItem, thread: Recipient): Boolean {
|
||||||
@ -121,7 +164,8 @@ object ConversationMenuHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun search(context: Context) {
|
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")
|
@SuppressLint("StaticFieldLeak")
|
||||||
@ -254,7 +298,6 @@ object ConversationMenuHelper {
|
|||||||
try {
|
try {
|
||||||
if (isClosedGroup) {
|
if (isClosedGroup) {
|
||||||
MessageSender.leave(groupPublicKey!!, true)
|
MessageSender.leave(groupPublicKey!!, true)
|
||||||
// TODO: Disable input?
|
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(context, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show()
|
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) {
|
private fun unmute(context: Context, thread: Recipient) {
|
||||||
thread.setMuted(0)
|
|
||||||
DatabaseFactory.getRecipientDatabase(context).setMuted(thread, 0)
|
DatabaseFactory.getRecipientDatabase(context).setMuted(thread, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mute(context: Context, thread: Recipient) {
|
private fun mute(context: Context, thread: Recipient) {
|
||||||
MuteDialog.show(context) { until: Long ->
|
MuteDialog.show(context) { until: Long ->
|
||||||
thread.setMuted(until)
|
|
||||||
DatabaseFactory.getRecipientDatabase(context).setMuted(thread, until)
|
DatabaseFactory.getRecipientDatabase(context).setMuted(thread, until)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,9 @@ class ControlMessageView : LinearLayout {
|
|||||||
if (message.isExpirationTimerUpdate) {
|
if (message.isExpirationTimerUpdate) {
|
||||||
iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_timer, context.theme))
|
iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_timer, context.theme))
|
||||||
iconImageView.visibility = View.VISIBLE
|
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)
|
textView.text = message.getDisplayBody(context)
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ class LinkPreviewView : LinearLayout {
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Updating
|
// 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()
|
val linkPreview = message.linkPreviews.first()
|
||||||
url = linkPreview.url
|
url = linkPreview.url
|
||||||
// Thumbnail
|
// Thumbnail
|
||||||
@ -60,7 +60,7 @@ class LinkPreviewView : LinearLayout {
|
|||||||
}
|
}
|
||||||
titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme))
|
titleTextView.setTextColor(ResourcesCompat.getColor(resources, textColorID, context.theme))
|
||||||
// Body
|
// Body
|
||||||
bodyTextView = VisibleMessageContentView.getBodyTextView(context, message)
|
bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
|
||||||
mainLinkPreviewContainer.addView(bodyTextView)
|
mainLinkPreviewContainer.addView(bodyTextView)
|
||||||
// Corner radii
|
// Corner radii
|
||||||
val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing)
|
val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing)
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
package org.thoughtcrime.securesms.conversation.v2.messages
|
package org.thoughtcrime.securesms.conversation.v2.messages
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.text.style.BackgroundColorSpan
|
||||||
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.text.style.ReplacementSpan
|
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
import android.text.util.Linkify
|
import android.text.util.Linkify
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.Log
|
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@ -24,6 +24,7 @@ import androidx.core.graphics.BlendModeCompat
|
|||||||
import androidx.core.text.getSpans
|
import androidx.core.text.getSpans
|
||||||
import androidx.core.text.toSpannable
|
import androidx.core.text.toSpannable
|
||||||
import androidx.core.text.util.LinkifyCompat
|
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 kotlinx.android.synthetic.main.view_visible_message_content.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.ThemeUtil
|
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.database.model.MmsMessageRecord
|
||||||
import org.thoughtcrime.securesms.loki.utilities.*
|
import org.thoughtcrime.securesms.loki.utilities.*
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
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
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class VisibleMessageContentView : LinearLayout {
|
class VisibleMessageContentView : LinearLayout {
|
||||||
var onContentClick: ((event: MotionEvent) -> Unit)? = null
|
var onContentClick: ((event: MotionEvent) -> Unit)? = null
|
||||||
var onContentDoubleTap: (() -> Unit)? = null
|
var onContentDoubleTap: (() -> Unit)? = null
|
||||||
|
var delegate: VisibleMessageContentViewDelegate? = null
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
constructor(context: Context) : super(context) { initialize() }
|
constructor(context: Context) : super(context) { initialize() }
|
||||||
@ -56,7 +61,7 @@ class VisibleMessageContentView : LinearLayout {
|
|||||||
|
|
||||||
// region Updating
|
// region Updating
|
||||||
fun bind(message: MessageRecord, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean,
|
fun bind(message: MessageRecord, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean,
|
||||||
glide: GlideRequests, maxWidth: Int, thread: Recipient) {
|
glide: GlideRequests, maxWidth: Int, thread: Recipient, searchQuery: String?) {
|
||||||
// Background
|
// Background
|
||||||
val background = getBackground(message.isOutgoing, isStartOfMessageCluster, isEndOfMessageCluster)
|
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
|
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
|
onContentDoubleTap = null
|
||||||
if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) {
|
if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) {
|
||||||
val linkPreviewView = LinkPreviewView(context)
|
val linkPreviewView = LinkPreviewView(context)
|
||||||
linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster)
|
linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster, searchQuery)
|
||||||
mainContainer.addView(linkPreviewView)
|
mainContainer.addView(linkPreviewView)
|
||||||
onContentClick = { event -> linkPreviewView.calculateHit(event) }
|
onContentClick = { event -> linkPreviewView.calculateHit(event) }
|
||||||
// Body text view is inside the link preview for layout convenience
|
// 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,
|
quoteView.bind(quote.author.toString(), quote.text, quote.attachment, thread,
|
||||||
message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation, message.threadId, glide)
|
message.isOutgoing, maxContentWidth, message.isOpenGroupInvitation, message.threadId, glide)
|
||||||
mainContainer.addView(quoteView)
|
mainContainer.addView(quoteView)
|
||||||
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message)
|
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
|
||||||
ViewUtil.setPaddingTop(bodyTextView, 0)
|
ViewUtil.setPaddingTop(bodyTextView, 0)
|
||||||
mainContainer.addView(bodyTextView)
|
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) {
|
} else if (message is MmsMessageRecord && message.slideDeck.audioSlide != null) {
|
||||||
val voiceMessageView = VoiceMessageView(context)
|
val voiceMessageView = VoiceMessageView(context)
|
||||||
voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster)
|
voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster)
|
||||||
@ -119,7 +131,7 @@ class VisibleMessageContentView : LinearLayout {
|
|||||||
mainContainer.addView(openGroupInvitationView)
|
mainContainer.addView(openGroupInvitationView)
|
||||||
onContentClick = { openGroupInvitationView.joinOpenGroup() }
|
onContentClick = { openGroupInvitationView.joinOpenGroup() }
|
||||||
} else {
|
} else {
|
||||||
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message)
|
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
|
||||||
mainContainer.addView(bodyTextView)
|
mainContainer.addView(bodyTextView)
|
||||||
onContentClick = { event ->
|
onContentClick = { event ->
|
||||||
// intersectedModalSpans should only be a list of one item
|
// intersectedModalSpans should only be a list of one item
|
||||||
@ -153,7 +165,7 @@ class VisibleMessageContentView : LinearLayout {
|
|||||||
// region Convenience
|
// region Convenience
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun getBodyTextView(context: Context, message: MessageRecord): TextView {
|
fun getBodyTextView(context: Context, message: MessageRecord, searchQuery: String?): TextView {
|
||||||
val result = EmojiTextView(context)
|
val result = EmojiTextView(context)
|
||||||
val vPadding = context.resources.getDimension(R.dimen.small_spacing).toInt()
|
val vPadding = context.resources.getDimension(R.dimen.small_spacing).toInt()
|
||||||
val hPadding = toPx(12, context.resources)
|
val hPadding = toPx(12, context.resources)
|
||||||
@ -179,6 +191,9 @@ class VisibleMessageContentView : LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context)
|
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
|
result.text = body
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -196,3 +211,8 @@ class VisibleMessageContentView : LinearLayout {
|
|||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface VisibleMessageContentViewDelegate {
|
||||||
|
|
||||||
|
fun scrollToMessageIfPossible(timestamp: Long)
|
||||||
|
}
|
@ -3,21 +3,27 @@ package org.thoughtcrime.securesms.conversation.v2.messages
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.ColorFilter
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.os.AsyncTask
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.RelativeLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import kotlinx.android.synthetic.main.view_visible_message.view.*
|
import kotlinx.android.synthetic.main.view_visible_message.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.messaging.contacts.Contact.ContactContext
|
import org.session.libsession.messaging.contacts.Contact.ContactContext
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||||
import org.session.libsession.utilities.ViewUtil
|
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.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||||
@ -47,6 +53,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
var onPress: ((event: MotionEvent) -> Unit)? = null
|
var onPress: ((event: MotionEvent) -> Unit)? = null
|
||||||
var onSwipeToReply: (() -> Unit)? = null
|
var onSwipeToReply: (() -> Unit)? = null
|
||||||
var onLongPress: (() -> Unit)? = null
|
var onLongPress: (() -> Unit)? = null
|
||||||
|
var contentViewDelegate: VisibleMessageContentViewDelegate? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val swipeToReplyThreshold = 80.0f // dp
|
const val swipeToReplyThreshold = 80.0f // dp
|
||||||
@ -69,7 +76,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Updating
|
// 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 sender = message.individualRecipient
|
||||||
val senderSessionID = sender.address.serialize()
|
val senderSessionID = sender.address.serialize()
|
||||||
val threadID = message.threadId
|
val threadID = message.threadId
|
||||||
@ -137,11 +144,14 @@ class VisibleMessageView : LinearLayout {
|
|||||||
} else {
|
} else {
|
||||||
messageStatusImageView.isVisible = false
|
messageStatusImageView.isVisible = false
|
||||||
}
|
}
|
||||||
|
// Expiration timer
|
||||||
|
updateExpirationTimer(message)
|
||||||
// Calculate max message bubble width
|
// Calculate max message bubble width
|
||||||
var maxWidth = screenWidth - messageContentContainerLayoutParams.leftMargin - messageContentContainerLayoutParams.rightMargin
|
var maxWidth = screenWidth - messageContentContainerLayoutParams.leftMargin - messageContentContainerLayoutParams.rightMargin
|
||||||
if (profilePictureContainer.visibility != View.GONE) { maxWidth -= profilePictureContainer.width }
|
if (profilePictureContainer.visibility != View.GONE) { maxWidth -= profilePictureContainer.width }
|
||||||
// Populate content view
|
// 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() }
|
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() {
|
private fun handleIsSelectedChanged() {
|
||||||
background = if (snIsSelected) {
|
background = if (snIsSelected) {
|
||||||
ColorDrawable(context.resources.getColorWithID(R.color.message_selected, context.theme))
|
ColorDrawable(context.resources.getColorWithID(R.color.message_selected, context.theme))
|
||||||
@ -242,6 +283,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
} else {
|
} else {
|
||||||
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
|
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
|
// The idea here is to asymptotically approach a maximum drag distance
|
||||||
val damping = 50.0f
|
val damping = 50.0f
|
||||||
val sign = -1.0f
|
val sign = -1.0f
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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<SearchResult>
|
||||||
|
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<SearchResult>
|
||||||
|
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<MessageResult?>
|
||||||
|
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<MessageResult?>
|
||||||
|
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<MessageResult?> ->
|
||||||
|
runOnMain {
|
||||||
|
if (searchOpen && query == activeQuery) {
|
||||||
|
result.setValue(SearchResult(messages, 0))
|
||||||
|
} else {
|
||||||
|
messages.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchResult(private val results: CursorList<MessageResult?>, val position: Int) : Closeable {
|
||||||
|
|
||||||
|
fun getResults(): List<MessageResult?> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -57,6 +57,7 @@ import org.session.libsession.utilities.recipients.Recipient;
|
|||||||
import org.session.libsession.utilities.recipients.RecipientFormattingException;
|
import org.session.libsession.utilities.recipients.RecipientFormattingException;
|
||||||
import org.session.libsignal.utilities.JsonUtil;
|
import org.session.libsignal.utilities.JsonUtil;
|
||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
|
import org.session.libsignal.utilities.ThreadUtils;
|
||||||
import org.session.libsignal.utilities.guava.Optional;
|
import org.session.libsignal.utilities.guava.Optional;
|
||||||
import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment;
|
import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment;
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
@ -881,9 +882,9 @@ public class MmsDatabase extends MessagingDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean delete(long messageId) {
|
public boolean delete(long messageId) {
|
||||||
long threadId = getThreadIdForMessage(messageId);
|
long threadId = getThreadIdForMessage(messageId);
|
||||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||||
attachmentDatabase.deleteAttachmentsForMessage(messageId);
|
ThreadUtils.queue(() -> attachmentDatabase.deleteAttachmentsForMessage(messageId));
|
||||||
|
|
||||||
GroupReceiptDatabase groupReceiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
|
GroupReceiptDatabase groupReceiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
|
||||||
groupReceiptDatabase.deleteRowsForMessage(messageId);
|
groupReceiptDatabase.deleteRowsForMessage(messageId);
|
||||||
|
@ -20,6 +20,8 @@ package org.thoughtcrime.securesms.database;
|
|||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
@ -411,7 +413,6 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
|
|
||||||
notifyConversationListeners(threadId);
|
notifyConversationListeners(threadId);
|
||||||
|
|
||||||
|
|
||||||
return Optional.of(new InsertResult(messageId, threadId));
|
return Optional.of(new InsertResult(messageId, threadId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -512,7 +513,7 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
public boolean deleteMessage(long messageId) {
|
public boolean deleteMessage(long messageId) {
|
||||||
Log.i("MessageDatabase", "Deleting: " + messageId);
|
Log.i("MessageDatabase", "Deleting: " + messageId);
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
long threadId = getThreadIdForMessage(messageId);
|
long threadId = getThreadIdForMessage(messageId);
|
||||||
db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
|
db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""});
|
||||||
boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
|
boolean threadDeleted = DatabaseFactory.getThreadDatabase(context).update(threadId, false);
|
||||||
notifyConversationListeners(threadId);
|
notifyConversationListeners(threadId);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.thoughtcrime.securesms.database
|
package org.thoughtcrime.securesms.database
|
||||||
|
|
||||||
import android.app.job.JobScheduler
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import org.session.libsession.database.StorageProtocol
|
import org.session.libsession.database.StorageProtocol
|
||||||
@ -106,7 +105,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
}
|
}
|
||||||
else -> Optional.absent()
|
else -> Optional.absent()
|
||||||
}
|
}
|
||||||
val pointerAttachments = attachments.mapNotNull {
|
val pointers = attachments.mapNotNull {
|
||||||
it.toSignalAttachment()
|
it.toSignalAttachment()
|
||||||
}
|
}
|
||||||
val targetAddress = if (isUserSender && !message.syncTarget.isNullOrEmpty()) {
|
val targetAddress = if (isUserSender && !message.syncTarget.isNullOrEmpty()) {
|
||||||
@ -122,7 +121,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
val linkPreviews: Optional<List<LinkPreview>> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! })
|
val linkPreviews: Optional<List<LinkPreview>> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! })
|
||||||
val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
|
val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
|
||||||
val insertResult = if (message.sender == getUserPublicKey()) {
|
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!!)
|
mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!)
|
||||||
} else {
|
} else {
|
||||||
// It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment
|
// It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment
|
||||||
|
@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.loki.utilities.fadeOut
|
|||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
import org.thoughtcrime.securesms.mms.GlideApp
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
|
|
||||||
//TODO Refactor to avoid using kotlinx.android.synthetic
|
//TODO Refactor to avoid using kotlinx.android.synthetic
|
||||||
class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
|
class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
|
||||||
@ -135,10 +136,10 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM
|
|||||||
|
|
||||||
// region Convenience
|
// region Convenience
|
||||||
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
||||||
val intent = Intent(context, ConversationActivity::class.java)
|
val intent = Intent(context, ConversationActivityV2::class.java)
|
||||||
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId)
|
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
|
||||||
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
|
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
|
||||||
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address)
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
@ -30,6 +30,7 @@ import org.session.libsession.utilities.recipients.Recipient
|
|||||||
import org.session.libsignal.utilities.PublicKeyValidation
|
import org.session.libsignal.utilities.PublicKeyValidation
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
|
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
|
||||||
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
|
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate
|
||||||
@ -111,12 +112,12 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC
|
|||||||
|
|
||||||
private fun createPrivateChat(hexEncodedPublicKey: String) {
|
private fun createPrivateChat(hexEncodedPublicKey: String) {
|
||||||
val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false)
|
val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false)
|
||||||
val intent = Intent(this, ConversationActivity::class.java)
|
val intent = Intent(this, ConversationActivityV2::class.java)
|
||||||
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address)
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA))
|
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA))
|
||||||
intent.setDataAndType(getIntent().data, getIntent().type)
|
intent.setDataAndType(getIntent().data, getIntent().type)
|
||||||
val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient)
|
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)
|
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
|
@ -33,6 +33,7 @@ import org.session.libsignal.utilities.PublicKeyValidation
|
|||||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
import org.thoughtcrime.securesms.groups.GroupManager
|
import org.thoughtcrime.securesms.groups.GroupManager
|
||||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
||||||
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
|
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
|
||||||
@ -127,10 +128,10 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
|
|||||||
|
|
||||||
// region Convenience
|
// region Convenience
|
||||||
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
||||||
val intent = Intent(context, ConversationActivity::class.java)
|
val intent = Intent(context, ConversationActivityV2::class.java)
|
||||||
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId)
|
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
|
||||||
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
|
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
|
||||||
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address)
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
@ -26,6 +26,7 @@ import org.session.libsession.utilities.recipients.Recipient
|
|||||||
import org.thoughtcrime.securesms.util.FileProviderUtil
|
import org.thoughtcrime.securesms.util.FileProviderUtil
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsignal.utilities.PublicKeyValidation
|
import org.session.libsignal.utilities.PublicKeyValidation
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
@ -53,12 +54,12 @@ class QRCodeActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperF
|
|||||||
fun createPrivateChatIfPossible(hexEncodedPublicKey: String) {
|
fun createPrivateChatIfPossible(hexEncodedPublicKey: String) {
|
||||||
if (!PublicKeyValidation.isValid(hexEncodedPublicKey)) { return Toast.makeText(this, R.string.invalid_session_id, Toast.LENGTH_SHORT).show() }
|
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 recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false)
|
||||||
val intent = Intent(this, ConversationActivity::class.java)
|
val intent = Intent(this, ConversationActivityV2::class.java)
|
||||||
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address)
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA))
|
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA))
|
||||||
intent.setDataAndType(getIntent().data, getIntent().type)
|
intent.setDataAndType(getIntent().data, getIntent().type)
|
||||||
val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient)
|
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)
|
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
|
@ -31,23 +31,12 @@ class ProfilePictureView : RelativeLayout {
|
|||||||
private val profilePicturesCache = mutableMapOf<String, String?>()
|
private val profilePicturesCache = mutableMapOf<String, String?>()
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
constructor(context: Context) : super(context) {
|
constructor(context: Context) : super(context) { initialize() }
|
||||||
setUpViewHierarchy()
|
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) {
|
private fun initialize() {
|
||||||
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() {
|
|
||||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||||
val contentView = inflater.inflate(R.layout.view_profile_picture, null)
|
val contentView = inflater.inflate(R.layout.view_profile_picture, null)
|
||||||
addView(contentView)
|
addView(contentView)
|
||||||
|
@ -49,6 +49,7 @@ import org.session.libsignal.utilities.Log;
|
|||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
|
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||||
@ -115,9 +116,9 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
|||||||
if (visibleThread == threadId) {
|
if (visibleThread == threadId) {
|
||||||
sendInThreadNotification(context, recipient);
|
sendInThreadNotification(context, recipient);
|
||||||
} else {
|
} else {
|
||||||
Intent intent = new Intent(context, ConversationActivity.class);
|
Intent intent = new Intent(context, ConversationActivityV2.class);
|
||||||
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress());
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress());
|
||||||
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
|
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
|
||||||
intent.setData((Uri.parse("custom://" + System.currentTimeMillis())));
|
intent.setData((Uri.parse("custom://" + System.currentTimeMillis())));
|
||||||
|
|
||||||
FailedNotificationBuilder builder = new FailedNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context), intent);
|
FailedNotificationBuilder builder = new FailedNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context), intent);
|
||||||
|
@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.core.app.TaskStackBuilder;
|
import androidx.core.app.TaskStackBuilder;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
|
|
||||||
@ -67,11 +68,11 @@ public class NotificationItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PendingIntent getPendingIntent(Context context) {
|
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;
|
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())));
|
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
|
||||||
|
|
||||||
return TaskStackBuilder.create(context)
|
return TaskStackBuilder.create(context)
|
||||||
|
@ -14,6 +14,7 @@ import android.widget.Toast;
|
|||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
|
|
||||||
@ -32,9 +33,9 @@ public class CommunicationActions {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Long threadId) {
|
protected void onPostExecute(Long threadId) {
|
||||||
Intent intent = new Intent(context, ConversationActivity.class);
|
Intent intent = new Intent(context, ConversationActivityV2.class);
|
||||||
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress());
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress());
|
||||||
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
|
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
|
||||||
intent.putExtra(ConversationActivity.TIMING_EXTRA, System.currentTimeMillis());
|
intent.putExtra(ConversationActivity.TIMING_EXTRA, System.currentTimeMillis());
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(text)) {
|
if (!TextUtils.isEmpty(text)) {
|
||||||
|
@ -34,6 +34,13 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true" />
|
android:layout_alignParentBottom="true" />
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
|
||||||
|
android:id="@+id/searchBottomBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/additionalContentContainer"
|
android:id="@+id/additionalContentContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
@ -9,8 +10,8 @@
|
|||||||
|
|
||||||
<org.thoughtcrime.securesms.loki.views.ProfilePictureView
|
<org.thoughtcrime.securesms.loki.views.ProfilePictureView
|
||||||
android:id="@+id/profilePictureView"
|
android:id="@+id/profilePictureView"
|
||||||
android:layout_width="@dimen/small_profile_picture_size"
|
android:layout_width="@dimen/medium_profile_picture_size"
|
||||||
android:layout_height="@dimen/small_profile_picture_size" />
|
android:layout_height="@dimen/medium_profile_picture_size" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
tools:text="30 mins"/>
|
tools:text="30 mins"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.ExpirationTimerView
|
<org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
|
||||||
android:id="@+id/footer_expiration_timer"
|
android:id="@+id/footer_expiration_timer"
|
||||||
android:layout_gravity="center_vertical|end"
|
android:layout_gravity="center_vertical|end"
|
||||||
android:layout_marginStart="6dp"
|
android:layout_marginStart="6dp"
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/menu_badge_icon"
|
android:id="@+id/menu_badge_icon"
|
||||||
android:layout_width="20dp"
|
android:layout_width="16dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="16dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/ic_timer"
|
android:src="@drawable/ic_timer"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingVertical="@dimen/medium_spacing"
|
android:paddingVertical="@dimen/medium_spacing"
|
||||||
android:paddingHorizontal="@dimen/massive_spacing"
|
android:paddingHorizontal="@dimen/massive_spacing"
|
||||||
@ -12,7 +13,8 @@
|
|||||||
android:id="@+id/iconImageView"
|
android:id="@+id/iconImageView"
|
||||||
android:layout_width="12dp"
|
android:layout_width="12dp"
|
||||||
android:layout_height="12dp"
|
android:layout_height="12dp"
|
||||||
android:layout_marginBottom="@dimen/small_spacing" />
|
android:layout_marginBottom="@dimen/small_spacing"
|
||||||
|
app:tint="@color/text" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView"
|
android:id="@+id/textView"
|
||||||
|
70
app/src/main/res/layout/view_search_bottom_bar.xml
Normal file
70
app/src/main/res/layout/view_search_bottom_bar.xml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/searchBottomBarConstraintLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/input_bar_height"
|
||||||
|
android:background="@color/input_bar_background"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:background="@color/separator" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/searchUp"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_baseline_keyboard_arrow_up_24"
|
||||||
|
android:tint="@color/accent"
|
||||||
|
tools:ignore="UseAppTint" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/searchDown"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_baseline_keyboard_arrow_down_24"
|
||||||
|
android:tint="@color/accent"
|
||||||
|
tools:ignore="UseAppTint" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/searchPosition"
|
||||||
|
style="@style/Signal.Text.Body"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:text="37 of 73"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<com.github.ybq.android.spinkit.SpinKitView
|
||||||
|
style="@style/SpinKitView.DoubleBounce"
|
||||||
|
android:id="@+id/searchProgressWheel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:background="@color/compose_view_background"
|
||||||
|
app:SpinKit_Color="?android:textColorPrimary"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -1,114 +1,126 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<RelativeLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/dateBreakTextView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:textSize="@dimen/very_small_font_size"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:gravity="center" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/mainContainer"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical">
|
||||||
android:gravity="bottom">
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dateBreakTextView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/very_small_font_size"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/profilePictureContainer"
|
android:id="@+id/mainContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:gravity="bottom">
|
||||||
|
|
||||||
<View
|
<LinearLayout
|
||||||
android:layout_width="12dp"
|
android:id="@+id/profilePictureContainer"
|
||||||
android:layout_height="0dp" />
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="26dp"
|
|
||||||
android:layout_height="32dp" >
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.loki.views.ProfilePictureView
|
|
||||||
android:id="@+id/profilePictureView"
|
|
||||||
android:layout_width="@dimen/very_small_profile_picture_size"
|
|
||||||
android:layout_height="@dimen/very_small_profile_picture_size"
|
|
||||||
android:layout_marginTop="3dp" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/moderatorIconImageView"
|
|
||||||
android:layout_width="16dp"
|
|
||||||
android:layout_height="16dp"
|
|
||||||
android:src="@drawable/ic_crown"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentBottom="true" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="12dp"
|
|
||||||
android:layout_height="0dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/messageContentContainer"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/senderNameTextView"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="4dp"
|
android:orientation="horizontal">
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:layout_marginEnd="12dp"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:ellipsize="end" />
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentView
|
<View
|
||||||
android:id="@+id/messageContentView"
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="0dp" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="26dp"
|
||||||
|
android:layout_height="32dp" >
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.loki.views.ProfilePictureView
|
||||||
|
android:id="@+id/profilePictureView"
|
||||||
|
android:layout_width="@dimen/very_small_profile_picture_size"
|
||||||
|
android:layout_height="@dimen/very_small_profile_picture_size"
|
||||||
|
android:layout_marginTop="3dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/moderatorIconImageView"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:src="@drawable/ic_crown"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentBottom="true" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="0dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/messageContentContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/messageTimestampTextView"
|
android:id="@+id/senderNameTextView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_marginBottom="4dp"
|
||||||
android:layout_centerVertical="true"
|
android:layout_marginStart="12dp"
|
||||||
android:layout_marginStart="2dp"
|
android:layout_marginEnd="12dp"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textStyle="bold"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textSize="10dp" />
|
android:ellipsize="end" />
|
||||||
|
|
||||||
<ImageView
|
<org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentView
|
||||||
android:id="@+id/messageStatusImageView"
|
android:id="@+id/messageContentView"
|
||||||
android:layout_width="16dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="16dp"
|
android:layout_height="wrap_content" />
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_marginEnd="2dp"
|
|
||||||
android:padding="2dp"
|
|
||||||
android:src="@drawable/ic_delivery_status_sent" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/messageTimestampTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginStart="2dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textSize="10dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/messageStatusImageView"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginEnd="2dp"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:src="@drawable/ic_delivery_status_sent" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
|
||||||
|
android:id="@+id/expirationTimerView"
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
android:layout_marginHorizontal="@dimen/medium_spacing"
|
||||||
|
android:layout_centerVertical="true" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
Loading…
x
Reference in New Issue
Block a user