feat: add view model storage and other activity logic for conv settings, including activity result for existing search convo logic

This commit is contained in:
0x330a
2022-11-02 17:31:37 +11:00
parent f5801a57de
commit 880a3f603c
9 changed files with 174 additions and 29 deletions

View File

@@ -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()
}
}
}
}
}

View File

@@ -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<Long, ConversationSettingsActivityResult>() {
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
}
}

View File

@@ -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 <T : ViewModel> create(modelClass: Class<T>): T {
return ConversationSettingsViewModel(threadId, storage) as T
}
}
}

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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<MarkedMessageInfo> messages = setRead(threadId, true);
if (isGroupRecipient) {

View File

@@ -9,16 +9,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/groupOptions"/>
<androidx.constraintlayout.widget.Group
android:id="@+id/adminControlsGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="adminContainer,adminSettingsTitle"/>
<include
android:id="@+id/profilePictureView"
layout="@layout/view_large_profile_picture"
@@ -205,6 +195,12 @@
</LinearLayout>
<!-- Admin settings -->
<androidx.constraintlayout.widget.Group
android:id="@+id/adminControlsGroup"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="adminContainer,adminSettingsTitle"/>
<TextView
style="@style/TextAppearance.Session.ConversationSettings.Subtitle"
android:text="@string/conversation_settings_admin_settings_title"

View File

@@ -867,6 +867,7 @@
<string name="conversation_settings_search">Search Conversation</string>
<string name="conversation_settings_all_media">All Media</string>
<string name="conversation_settings_pin_conversation">Pin Conversation</string>
<string name="conversation_settings_unpin_conversation">Unpin Conversation</string>
<string name="conversation_settings_notifications">Notifications</string>
<string name="conversation_settings_auto_download_title">Auto-download Media</string>
<string name="conversation_settings_auto_download_summary">Automatically download media and files from this chat.</string>

View File

@@ -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?