diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 00a45ba435..31c2466ae2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -228,6 +228,10 @@ + () { + + override fun createIntent(context: Context, input: Long?): Intent = + Intent(context, ConversationNotificationSettingsActivity::class.java).apply { + putExtra(ConversationActivityV2.THREAD_ID, input) + } + + override fun parseResult(resultCode: Int, intent: Intent?) { /* do nothing */ } +} \ No newline at end of file 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 2cb1c5997d..851ae8e25a 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 @@ -27,6 +27,18 @@ class ConversationSettingsActivity: PassphraseRequiredActionBarActivity(), View. lateinit var binding: ActivityConversationSettingsBinding + private val groupOptions: List + get() = with(binding) { + listOf( + groupMembers, + groupMembersDivider.root, + editGroup, + editGroupDivider.root, + leaveGroup, + leaveGroupDivider.root + ) + } + @Inject lateinit var threadDb: ThreadDatabase @Inject lateinit var lokiThreadDb: LokiThreadDatabase @Inject lateinit var viewModelFactory: ConversationSettingsViewModel.AssistedFactory @@ -38,6 +50,10 @@ class ConversationSettingsActivity: PassphraseRequiredActionBarActivity(), View. viewModelFactory.create(threadId) } + private val notificationActivityCallback = registerForActivityResult(ConversationNotificationSettingsActivityContract()) { + updateRecipientDisplay() + } + override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { super.onCreate(savedInstanceState, ready) binding = ActivityConversationSettingsBinding.inflate(layoutInflater) @@ -47,16 +63,12 @@ class ConversationSettingsActivity: PassphraseRequiredActionBarActivity(), View. binding.searchConversation.setOnClickListener(this) binding.allMedia.setOnClickListener(this) binding.pinConversation.setOnClickListener(this) - } - - override fun onResume() { - super.onResume() - - } - - override fun onPause() { - super.onPause() - + binding.notificationSettings.setOnClickListener(this) + binding.back.setOnClickListener(this) + binding.autoDownloadMediaSwitch.setOnCheckedChangeListener { _, isChecked -> + viewModel.setTrusted(isChecked) + updateRecipientDisplay() + } } private fun updateRecipientDisplay() { @@ -72,35 +84,66 @@ class ConversationSettingsActivity: PassphraseRequiredActionBarActivity(), View. binding.conversationSubtitle.isVisible = recipient.isClosedGroupRecipient.apply { binding.conversationSubtitle.text = "TODO: This is a test for group descriptions" } + // Toggle group-specific settings + val areGroupOptionsVisible = recipient.isClosedGroupRecipient + groupOptions.forEach { v -> + v.isVisible = areGroupOptionsVisible + } + + // Group admin settings + val isUserGroupAdmin = areGroupOptionsVisible && viewModel.isUserGroupAdmin() + with (binding) { + groupMembersDivider.root.isVisible = areGroupOptionsVisible && !isUserGroupAdmin + groupMembers.isVisible = areGroupOptionsVisible && !isUserGroupAdmin + adminControlsGroup.isVisible = isUserGroupAdmin + deleteGroup.isVisible = isUserGroupAdmin + clearMessagesDivider.root.isVisible = isUserGroupAdmin + } // 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 + val trusted = viewModel.isTrusted() + binding.autoDownloadMediaSwitch.isChecked = trusted + + // Set notification type + val notifyTypes = resources.getStringArray(R.array.notify_types) + val summary = notifyTypes.getOrNull(recipient.notifyType) + binding.notificationsValue.text = summary } 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) + when { + v === binding.searchConversation -> { + setResult(RESULT_SEARCH) + finish() } - 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() + v === binding.allMedia -> { + val threadRecipient = viewModel.recipient ?: return + val intent = Intent(this, MediaOverviewActivity::class.java).apply { + putExtra(MediaOverviewActivity.ADDRESS_EXTRA, threadRecipient.address) } + startActivity(intent) + } + v === binding.pinConversation -> { + viewModel.togglePin().invokeOnCompletion { e -> + if (e != null) { + // something happened + Log.e("ConversationSettings", "Failed to toggle pin on thread", e) + } else { + updateRecipientDisplay() + } + } + } + v === binding.notificationSettings -> { + notificationActivityCallback.launch(viewModel.threadId) } + v === binding.back -> onBackPressed() } } } \ 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 c7873ebe12..526826c2f1 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 @@ -6,11 +6,15 @@ import androidx.lifecycle.viewModelScope import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.launch +import org.session.libsession.database.StorageProtocol +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.database.Storage class ConversationSettingsViewModel( val threadId: Long, - private val storage: Storage + private val storage: StorageProtocol, + private val prefs: TextSecurePreferences ): ViewModel() { val recipient get() = storage.getRecipientForThread(threadId) @@ -22,6 +26,22 @@ class ConversationSettingsViewModel( storage.setThreadPinned(threadId, !isPinned) } + fun isTrusted() = recipient?.let { recipient -> + storage.isContactTrusted(recipient) + } ?: false + + fun setTrusted(isTrusted: Boolean) { + val recipient = recipient ?: return + storage.setContactTrusted(recipient, isTrusted) + } + + fun isUserGroupAdmin(): Boolean = recipient?.let { recipient -> + if (!recipient.isGroupRecipient) return@let false + val localUserAddress = prefs.getLocalNumber() ?: return@let false + val group = storage.getGroup(recipient.address.toGroupString()) + group?.admins?.contains(Address.fromSerialized(localUserAddress)) ?: false // this will have to be replaced for new closed groups + } ?: false + // DI-related @dagger.assisted.AssistedFactory interface AssistedFactory { @@ -29,11 +49,12 @@ class ConversationSettingsViewModel( } class Factory @AssistedInject constructor( @Assisted private val threadId: Long, - private val storage: Storage + private val storage: Storage, + private val prefs: TextSecurePreferences ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return ConversationSettingsViewModel(threadId, storage) as T + return ConversationSettingsViewModel(threadId, storage, prefs) as T } } 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 d6052421be..4dd6764eff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -685,6 +685,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } + override fun isContactTrusted(recipient: Recipient): Boolean { + val sessionID = recipient.address.toString() + val contactDb = DatabaseComponent.get(context).sessionContactDatabase() + val contact = contactDb.getContactWithSessionID(sessionID) ?: return false + return contact.isTrusted + } + + override fun setContactTrusted(recipient: Recipient, isTrusted: Boolean) { + val sessionID = recipient.address.toString() + val contactDb = DatabaseComponent.get(context).sessionContactDatabase() + val contact = contactDb.getContactWithSessionID(sessionID) ?: return + val threadID = DatabaseComponent.get(context).threadDatabase().getThreadIdIfExistsFor(recipient) + contactDb.setContactIsTrusted(contact, isTrusted, threadID) + } + override fun getLastUpdated(threadID: Long): Long { val threadDB = DatabaseComponent.get(context).threadDatabase() return threadDB.getLastUpdated(threadID) diff --git a/app/src/main/res/layout/activity_conversation_notification_settings.xml b/app/src/main/res/layout/activity_conversation_notification_settings.xml new file mode 100644 index 0000000000..5482601caf --- /dev/null +++ b/app/src/main/res/layout/activity_conversation_notification_settings.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_conversation_settings.xml b/app/src/main/res/layout/activity_conversation_settings.xml index 73f72b0970..c12a9857ee 100644 --- a/app/src/main/res/layout/activity_conversation_settings.xml +++ b/app/src/main/res/layout/activity_conversation_settings.xml @@ -9,6 +9,16 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> + + + + diff --git a/app/src/main/res/layout/view_large_profile_picture.xml b/app/src/main/res/layout/view_large_profile_picture.xml index 559f6c5a1b..ac714f45b7 100644 --- a/app/src/main/res/layout/view_large_profile_picture.xml +++ b/app/src/main/res/layout/view_large_profile_picture.xml @@ -7,24 +7,26 @@ + android:layout_width="@dimen/extra_large_profile_picture_size" + android:layout_height="@dimen/extra_large_profile_picture_size"> + android:background="@drawable/profile_picture_view_large_background" /> + android:background="@drawable/profile_picture_view_large_background" /> diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 566ee7e36c..55a2a2d381 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -234,6 +234,7 @@ @string/notify_type_all @string/notify_type_mentions + @string/notify_type_mute diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fe3191203d..97265c6760 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -748,8 +748,9 @@ Warning This is your recovery phrase. If you send it to someone they\'ll have full access to your account. Send - All - Mentions + All Messages + Mentions Only + Mute This message has been deleted Delete just for me Delete for everyone @@ -877,4 +878,6 @@ Add Admins Clear Messages Leave Group + Group Members + Delete Group 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 f8d000199c..7d529e7f25 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -163,6 +163,8 @@ interface StorageProtocol { fun getRecipientForThread(threadId: Long): Recipient? fun getRecipientSettings(address: Address): RecipientSettings? fun addContacts(contacts: List) + fun isContactTrusted(recipient: Recipient): Boolean + fun setContactTrusted(recipient: Recipient, isTrusted: Boolean) // Attachments fun getAttachmentDataUri(attachmentId: AttachmentId): Uri