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