From 880a3f603c1838797f8d94772ec2c806e3efc8d0 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:31:37 +1100 Subject: [PATCH] feat: add view model storage and other activity logic for conv settings, including activity result for existing search convo logic --- .../settings/ConversationSettingsActivity.kt | 97 ++++++++++++++++--- .../ConversationSettingsActivityContracts.kt | 24 +++++ .../settings/ConversationSettingsViewModel.kt | 33 ++++++- .../conversation/v2/ConversationActivityV2.kt | 15 ++- .../securesms/database/Storage.kt | 8 ++ .../securesms/database/ThreadDatabase.java | 7 ++ .../layout/activity_conversation_settings.xml | 16 ++- app/src/main/res/values/strings.xml | 1 + .../libsession/database/StorageProtocol.kt | 2 + 9 files changed, 174 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivityContracts.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivity.kt index 63bf53d8a7..2cb1c5997d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivity.kt @@ -1,37 +1,106 @@ package org.thoughtcrime.securesms.conversation.settings -import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.activity.viewModels import androidx.core.view.isVisible import dagger.hilt.android.AndroidEntryPoint +import network.loki.messenger.R import network.loki.messenger.databinding.ActivityConversationSettingsBinding +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.MediaOverviewActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog -import org.thoughtcrime.securesms.util.ActivityDispatcher +import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 +import org.thoughtcrime.securesms.database.LokiThreadDatabase +import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.mms.GlideApp +import javax.inject.Inject @AndroidEntryPoint -class ConversationSettingsActivity: PassphraseRequiredActionBarActivity(), ActivityDispatcher { +class ConversationSettingsActivity: PassphraseRequiredActionBarActivity(), View.OnClickListener { + + companion object { + // used to trigger displaying conversation search in calling parent activity + const val RESULT_SEARCH = 22 + } lateinit var binding: ActivityConversationSettingsBinding - val viewModel: ConversationSettingsViewModel by viewModels() - override fun dispatchIntent(body: (Context) -> Intent?) { - TODO() + @Inject lateinit var threadDb: ThreadDatabase + @Inject lateinit var lokiThreadDb: LokiThreadDatabase + @Inject lateinit var viewModelFactory: ConversationSettingsViewModel.AssistedFactory + val viewModel: ConversationSettingsViewModel by viewModels { + val threadId = intent.getLongExtra(ConversationActivityV2.THREAD_ID, -1L) + if (threadId == -1L) { + finish() + } + viewModelFactory.create(threadId) } - override fun showDialog(baseDialog: BaseDialog, tag: String?) { - TODO() - } - - override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { super.onCreate(savedInstanceState, ready) binding = ActivityConversationSettingsBinding.inflate(layoutInflater) setContentView(binding.root) - with (binding) { - adminControlsGroup.isVisible = false + binding.profilePictureView.root.glide = GlideApp.with(this) + updateRecipientDisplay() + binding.searchConversation.setOnClickListener(this) + binding.allMedia.setOnClickListener(this) + binding.pinConversation.setOnClickListener(this) + } + + override fun onResume() { + super.onResume() + + } + + override fun onPause() { + super.onPause() + + } + + private fun updateRecipientDisplay() { + val recipient = viewModel.recipient ?: return + // Setup profile image + binding.profilePictureView.root.update(recipient) + // Setup name + binding.conversationName.text = when { + recipient.isLocalNumber -> getString(R.string.note_to_self) + else -> recipient.toShortString() + } + // Setup group description (if group) + binding.conversationSubtitle.isVisible = recipient.isClosedGroupRecipient.apply { + binding.conversationSubtitle.text = "TODO: This is a test for group descriptions" + } + // Toggle group-specific settings + + // Set pinned state + binding.pinConversation.setText( + if (viewModel.isPinned()) R.string.conversation_settings_unpin_conversation + else R.string.conversation_settings_pin_conversation + ) + // Set auto-download state + } + + override fun onClick(v: View?) { + if (v === binding.searchConversation) { + setResult(RESULT_SEARCH) + finish() + } else if (v === binding.allMedia) { + val threadRecipient = viewModel.recipient ?: return + val intent = Intent(this, MediaOverviewActivity::class.java).apply { + putExtra(MediaOverviewActivity.ADDRESS_EXTRA, threadRecipient.address) + } + startActivity(intent) + } else if (v === binding.pinConversation) { + viewModel.togglePin().invokeOnCompletion { e -> + if (e != null) { + // something happened + Log.e("ConversationSettings", "Failed to toggle pin on thread", e) + } else { + updateRecipientDisplay() + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivityContracts.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivityContracts.kt new file mode 100644 index 0000000000..aa06ebcd7d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivityContracts.kt @@ -0,0 +1,24 @@ +package org.thoughtcrime.securesms.conversation.settings + +import android.content.Context +import android.content.Intent +import androidx.activity.result.contract.ActivityResultContract +import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 + +sealed class ConversationSettingsActivityResult { + object Finished: ConversationSettingsActivityResult() + object SearchConversation: ConversationSettingsActivityResult() +} + +class ConversationSettingsActivityContract: ActivityResultContract() { + + override fun createIntent(context: Context, input: Long?) = Intent(context, ConversationSettingsActivity::class.java).apply { + putExtra(ConversationActivityV2.THREAD_ID, input ?: -1L) + } + + override fun parseResult(resultCode: Int, intent: Intent?): ConversationSettingsActivityResult = + when (resultCode) { + ConversationSettingsActivity.RESULT_SEARCH -> ConversationSettingsActivityResult.SearchConversation + else -> ConversationSettingsActivityResult.Finished + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsViewModel.kt index 71d8b5352a..c7873ebe12 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsViewModel.kt @@ -1,9 +1,40 @@ package org.thoughtcrime.securesms.conversation.settings import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import kotlinx.coroutines.launch +import org.thoughtcrime.securesms.database.Storage -class ConversationSettingsViewModel: ViewModel() { +class ConversationSettingsViewModel( + val threadId: Long, + private val storage: Storage +): ViewModel() { + val recipient get() = storage.getRecipientForThread(threadId) + fun isPinned() = storage.isThreadPinned(threadId) + + fun togglePin() = viewModelScope.launch { + val isPinned = storage.isThreadPinned(threadId) + storage.setThreadPinned(threadId, !isPinned) + } + + // DI-related + @dagger.assisted.AssistedFactory + interface AssistedFactory { + fun create(threadId: Long): Factory + } + class Factory @AssistedInject constructor( + @Assisted private val threadId: Long, + private val storage: Storage + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return ConversationSettingsViewModel(threadId, storage) as T + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 71fdbae39d..fb854466d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -90,7 +90,8 @@ import org.thoughtcrime.securesms.attachments.ScreenshotObserver import org.thoughtcrime.securesms.audio.AudioRecorder import org.thoughtcrime.securesms.contacts.SelectContactsActivity.Companion.selectedContactsKey import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher -import org.thoughtcrime.securesms.conversation.settings.ConversationSettingsActivity +import org.thoughtcrime.securesms.conversation.settings.ConversationSettingsActivityContract +import org.thoughtcrime.securesms.conversation.settings.ConversationSettingsActivityResult import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnActionSelectedListener import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnReactionSelectedListener import org.thoughtcrime.securesms.conversation.v2.dialogs.BlockedDialog @@ -201,6 +202,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } + private val conversationSettingsCallback = registerForActivityResult(ConversationSettingsActivityContract()) { result -> + if (result is ConversationSettingsActivityResult.SearchConversation) { + // open search + binding?.toolbar?.menu?.findItem(R.id.menu_search)?.expandActionView() + } + } + private val screenWidth = Resources.getSystem().displayMetrics.widthPixels private val linkPreviewViewModel: LinkPreviewViewModel by lazy { ViewModelProvider(this, LinkPreviewViewModel.Factory(LinkPreviewRepository())) @@ -329,7 +337,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe const val PICK_GIF = 10 const val PICK_FROM_LIBRARY = 12 const val INVITE_CONTACTS = 124 - + const val CONVERSATION_SETTINGS = 125 // used to open conversation search on result } // endregion @@ -963,8 +971,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun onClick(v: View?) { if (v === binding?.toolbarContent?.profilePictureView?.root) { // open conversation settings - val intent = Intent(this, ConversationSettingsActivity::class.java) - startActivity(intent) + conversationSettingsCallback.launch(viewModel.threadId) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 76849b8af6..d6052421be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -705,7 +705,15 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return mmsSmsDb.getConversationCount(threadID) } + override fun setThreadPinned(threadID: Long, isPinned: Boolean) { + val threadDb = DatabaseComponent.get(context).threadDatabase() + threadDb.setPinned(threadID, isPinned) + } + override fun isThreadPinned(threadID: Long): Boolean { + val threadDb = DatabaseComponent.get(context).threadDatabase() + return threadDb.getPinned(threadID) + } override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri { return PartAuthority.getAttachmentDataUri(attachmentId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 02a82de1d4..6b892bdef3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -711,6 +711,13 @@ public class ThreadDatabase extends Database { notifyConversationListeners(threadId); } + public boolean getPinned(long threadId) { + Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[]{IS_PINNED}, ID_WHERE, new String[] {""+threadId},null, null, null); + boolean isPinned = cursor.moveToNext() && cursor.getInt(0) == 1; + cursor.close(); + return isPinned; + } + public void markAllAsRead(long threadId, boolean isGroupRecipient) { List messages = setRead(threadId, true); if (isGroupRecipient) { diff --git a/app/src/main/res/layout/activity_conversation_settings.xml b/app/src/main/res/layout/activity_conversation_settings.xml index 98450498d2..73f72b0970 100644 --- a/app/src/main/res/layout/activity_conversation_settings.xml +++ b/app/src/main/res/layout/activity_conversation_settings.xml @@ -9,16 +9,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - - + Search Conversation All Media Pin Conversation + Unpin Conversation Notifications Auto-download Media Automatically download media and files from this chat. diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index f35e5de0d4..f8d000199c 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -153,6 +153,8 @@ interface StorageProtocol { fun trimThread(threadID: Long, threadLimit: Int) fun trimThreadBefore(threadID: Long, timestamp: Long) fun getMessageCount(threadID: Long): Long + fun setThreadPinned(threadID: Long, isPinned: Boolean) + fun isThreadPinned(threadID: Long): Boolean // Contacts fun getContactWithSessionID(sessionID: String): Contact?