Merge pull request #1669 from oxen-io/feature/dynamic-community-rights

Listening to changes in community write access
This commit is contained in:
ThomasSession 2024-09-16 10:09:58 +10:00 committed by GitHub
commit d044f12d3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 70 additions and 20 deletions

View File

@ -710,7 +710,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// called from onCreate // called from onCreate
private fun setUpInputBar() { private fun setUpInputBar() {
binding.inputBar.isGone = viewModel.hidesInputBar()
binding.inputBar.delegate = this binding.inputBar.delegate = this
binding.inputBarRecordingView.delegate = this binding.inputBarRecordingView.delegate = this
// GIF button // GIF button
@ -854,6 +853,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// Conversation should be deleted now, just go back // Conversation should be deleted now, just go back
finish() finish()
} }
binding.inputBar.isGone = uiState.hideInputBar
} }
} }
} }
@ -948,11 +949,20 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
block(deleteThread = true) block(deleteThread = true)
} }
binding.declineMessageRequestButton.setOnClickListener { binding.declineMessageRequestButton.setOnClickListener {
viewModel.declineMessageRequest() fun doDecline() {
lifecycleScope.launch(Dispatchers.IO) { viewModel.declineMessageRequest()
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@ConversationActivityV2) lifecycleScope.launch(Dispatchers.IO) {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@ConversationActivityV2)
}
finish()
}
showSessionDialog {
title(R.string.delete)
text(resources.getString(R.string.messageRequestsDelete))
dangerButton(R.string.delete) { doDecline() }
button(R.string.cancel)
} }
finish()
} }
} }

View File

@ -9,8 +9,12 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
@ -29,6 +33,7 @@ import org.thoughtcrime.securesms.audio.AudioSlidePlayer
import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.groups.OpenGroupManager
import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.repository.ConversationRepository
import java.util.UUID import java.util.UUID
@ -65,6 +70,8 @@ class ConversationViewModel(
} }
} }
private var communityWriteAccessJob: Job? = null
private var _openGroup: RetrieveOnce<OpenGroup> = RetrieveOnce { private var _openGroup: RetrieveOnce<OpenGroup> = RetrieveOnce {
storage.getOpenGroup(threadId) storage.getOpenGroup(threadId)
} }
@ -105,6 +112,27 @@ class ConversationViewModel(
} }
} }
} }
// listen to community write access updates from this point
communityWriteAccessJob?.cancel()
communityWriteAccessJob = viewModelScope.launch {
OpenGroupManager.getCommunitiesWriteAccessFlow()
.map {
if(openGroup?.groupId != null)
it[openGroup?.groupId]
else null
}
.filterNotNull()
.collect{
// update our community object
_openGroup.updateTo(openGroup?.copy(canWrite = it))
// when we get an update on the write access of a community
// we need to update the input text accordingly
_uiState.update { state ->
state.copy(hideInputBar = shouldHideInputBar())
}
}
}
} }
override fun onCleared() { override fun onCleared() {
@ -267,7 +295,7 @@ class ConversationViewModel(
* - We are dealing with a contact from a community (blinded recipient) that does not allow * - We are dealing with a contact from a community (blinded recipient) that does not allow
* requests form community members * requests form community members
*/ */
fun hidesInputBar(): Boolean = openGroup?.canWrite == false || fun shouldHideInputBar(): Boolean = openGroup?.canWrite == false ||
blindedRecipient?.blocksCommunityMessageRequests == true blindedRecipient?.blocksCommunityMessageRequests == true
fun legacyBannerRecipient(context: Context): Recipient? = recipient?.run { fun legacyBannerRecipient(context: Context): Recipient? = recipient?.run {
@ -311,7 +339,8 @@ data class UiMessage(val id: Long, val message: String)
data class ConversationUiState( data class ConversationUiState(
val uiMessages: List<UiMessage> = emptyList(), val uiMessages: List<UiMessage> = emptyList(),
val isMessageRequestAccepted: Boolean? = null, val isMessageRequestAccepted: Boolean? = null,
val conversationExists: Boolean val conversationExists: Boolean,
val hideInputBar: Boolean = false
) )
data class RetrieveOnce<T>(val retrieval: () -> T?) { data class RetrieveOnce<T>(val retrieval: () -> T?) {

View File

@ -18,7 +18,7 @@ class LinkPreviewDialog(private val onEnabled: () -> Unit) : DialogFragment() {
title(R.string.linkPreviewsEnable) title(R.string.linkPreviewsEnable)
val txt = context.getSubbedCharSequence(R.string.linkPreviewsFirstDescription, APP_NAME_KEY to APP_NAME) val txt = context.getSubbedCharSequence(R.string.linkPreviewsFirstDescription, APP_NAME_KEY to APP_NAME)
text(txt) text(txt)
button(R.string.enable) { enable() } dangerButton(R.string.enable) { enable() }
cancelButton { dismiss() } cancelButton { dismiss() }
} }

View File

@ -4,6 +4,9 @@ import android.content.Context
import android.widget.Toast import android.widget.Toast
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.squareup.phrase.Phrase import com.squareup.phrase.Phrase
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.util.concurrent.Executors import java.util.concurrent.Executors
import network.loki.messenger.R import network.loki.messenger.R
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
@ -39,6 +42,9 @@ object OpenGroupManager {
return true return true
} }
// flow holding information on write access for our current communities
private val _communityWriteAccess: MutableStateFlow<Map<String, Boolean>> = MutableStateFlow(emptyMap())
fun startPolling() { fun startPolling() {
if (isPolling) { return } if (isPolling) { return }
isPolling = true isPolling = true
@ -66,6 +72,8 @@ object OpenGroupManager {
} }
} }
fun getCommunitiesWriteAccessFlow() = _communityWriteAccess.asStateFlow()
@WorkerThread @WorkerThread
fun add(server: String, room: String, publicKey: String, context: Context): Pair<Long,OpenGroupApi.RoomInfo?> { fun add(server: String, room: String, publicKey: String, context: Context): Pair<Long,OpenGroupApi.RoomInfo?> {
val openGroupID = "$server.$room" val openGroupID = "$server.$room"
@ -164,9 +172,13 @@ object OpenGroupManager {
fun updateOpenGroup(openGroup: OpenGroup, context: Context) { fun updateOpenGroup(openGroup: OpenGroup, context: Context) {
val threadDB = DatabaseComponent.get(context).lokiThreadDatabase() val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
val openGroupID = "${openGroup.server}.${openGroup.room}" val threadID = GroupManager.getOpenGroupThreadID(openGroup.groupId, context)
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
threadDB.setOpenGroupChat(openGroup, threadID) threadDB.setOpenGroupChat(openGroup, threadID)
// update write access for this community
val writeAccesses = _communityWriteAccess.value.toMutableMap()
writeAccesses[openGroup.groupId] = openGroup.canWrite
_communityWriteAccess.value = writeAccesses
} }
fun isUserModerator(context: Context, groupId: String, standardPublicKey: String, blindedPublicKey: String? = null): Boolean { fun isUserModerator(context: Context, groupId: String, standardPublicKey: String, blindedPublicKey: String? = null): Boolean {

View File

@ -19,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.SessionShieldIcon import org.thoughtcrime.securesms.ui.SessionShieldIcon
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
import org.thoughtcrime.securesms.ui.components.SlimPrimaryOutlineButton import org.thoughtcrime.securesms.ui.components.SlimPrimaryOutlineButton
import org.thoughtcrime.securesms.ui.contentDescription import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.theme.LocalColors import org.thoughtcrime.securesms.ui.theme.LocalColors
@ -60,8 +61,8 @@ internal fun SeedReminder(startRecoveryPasswordActivity: () -> Unit) {
style = LocalType.current.small style = LocalType.current.small
) )
} }
Spacer(Modifier.width(LocalDimensions.current.xsSpacing)) Spacer(Modifier.width(LocalDimensions.current.smallSpacing))
SlimPrimaryOutlineButton( PrimaryOutlineButton(
text = stringResource(R.string.theContinue), text = stringResource(R.string.theContinue),
modifier = Modifier modifier = Modifier
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)

View File

@ -108,7 +108,7 @@ class MessageRequestsActivity : PassphraseRequiredActionBarActivity(), Conversat
showSessionDialog { showSessionDialog {
title(R.string.delete) title(R.string.delete)
text(resources.getString(R.string.messageRequestsDelete)) text(resources.getString(R.string.messageRequestsDelete))
button(R.string.delete) { doDecline() } dangerButton(R.string.delete) { doDecline() }
button(R.string.cancel) button(R.string.cancel)
} }
} }
@ -129,9 +129,10 @@ class MessageRequestsActivity : PassphraseRequiredActionBarActivity(), Conversat
} }
showSessionDialog { showSessionDialog {
title(resources.getString(R.string.clearAll))
text(resources.getString(R.string.messageRequestsClearAllExplanation)) text(resources.getString(R.string.messageRequestsClearAllExplanation))
button(R.string.yes) { doDeleteAllAndBlock() } dangerButton(R.string.clear) { doDeleteAllAndBlock() }
button(R.string.no) button(R.string.cancel)
} }
} }
} }

View File

@ -7,9 +7,6 @@
android:title="@string/helpReportABug" android:title="@string/helpReportABug"
android:summary="@string/helpReportABugExportLogsDescription" android:summary="@string/helpReportABugExportLogsDescription"
android:widgetLayout="@layout/export_logs_widget" /> android:widgetLayout="@layout/export_logs_widget" />
<!-- Note: Having this as `android:layout` rather than `android:layoutWidget` allows it to fit the screen width -->
<Preference android:layout="@layout/preference_widget_progress" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory> <PreferenceCategory>

View File

@ -203,7 +203,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
@Test @Test
fun `local recipient should have input and no blinded recipient`() { fun `local recipient should have input and no blinded recipient`() {
whenever(recipient.isLocalNumber).thenReturn(true) whenever(recipient.isLocalNumber).thenReturn(true)
assertThat(viewModel.hidesInputBar(), equalTo(false)) assertThat(viewModel.shouldHideInputBar(), equalTo(false))
assertThat(viewModel.blindedRecipient, nullValue()) assertThat(viewModel.blindedRecipient, nullValue())
} }
@ -215,7 +215,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
} }
whenever(repository.maybeGetBlindedRecipient(recipient)).thenReturn(blinded) whenever(repository.maybeGetBlindedRecipient(recipient)).thenReturn(blinded)
assertThat(viewModel.blindedRecipient, notNullValue()) assertThat(viewModel.blindedRecipient, notNullValue())
assertThat(viewModel.hidesInputBar(), equalTo(true)) assertThat(viewModel.shouldHideInputBar(), equalTo(true))
} }
} }