Merge pull request #3704 from vector-im/feature/bca/promote_restricted

Promote restricted join rule to admins
This commit is contained in:
Valere 2021-08-27 17:27:57 +02:00 committed by GitHub
commit 4107d2643c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 320 additions and 24 deletions

1
changelog.d/3631.feature Normal file
View file

@ -0,0 +1 @@
Restricted Join Rule | Inform admins of new option

View file

@ -84,7 +84,7 @@ internal data class RoomVersions(
* }
* }
*/
@Json(name = "room_capabilities")
@Json(name = "org.matrix.msc3244.room_capabilities")
val roomCapabilities: JsonDict? = null
)

View file

@ -24,12 +24,12 @@ import android.os.Bundle
import android.os.Parcelable
import android.view.Menu
import android.view.MenuItem
import com.google.android.material.appbar.MaterialToolbar
import androidx.core.view.GravityCompat
import androidx.core.view.isVisible
import androidx.drawerlayout.widget.DrawerLayout
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.viewModel
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.AppStateHandler
import im.vector.app.R
@ -58,6 +58,7 @@ import im.vector.app.features.rageshake.ReportType
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.spaces.RestrictedPromoBottomSheet
import im.vector.app.features.spaces.SpaceCreationActivity
import im.vector.app.features.spaces.SpacePreviewActivity
import im.vector.app.features.spaces.SpaceSettingsMenuBottomSheet
@ -89,6 +90,7 @@ class HomeActivity :
UnknownDeviceDetectorSharedViewModel.Factory,
ServerBackupStatusViewModel.Factory,
UnreadMessagesSharedViewModel.Factory,
PromoteRestrictedViewModel.Factory,
NavigationInterceptor,
SpaceInviteBottomSheet.InteractionListener {
@ -99,6 +101,8 @@ class HomeActivity :
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel()
@Inject lateinit var serverBackupviewModelFactory: ServerBackupStatusViewModel.Factory
@Inject lateinit var promoteRestrictedViewModelFactory: PromoteRestrictedViewModel.Factory
private val promoteRestrictedViewModel: PromoteRestrictedViewModel by viewModel()
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
@ -173,18 +177,13 @@ class HomeActivity :
replaceFragment(R.id.homeDrawerFragmentContainer, HomeDrawerFragment::class.java)
}
// appStateHandler.selectedRoomGroupingObservable.subscribe {
// if (supportFragmentManager.getFragment())
// replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
// }.disposeOnDestroy()
sharedActionViewModel
.observe()
.subscribe { sharedAction ->
when (sharedAction) {
is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START)
is HomeActivitySharedAction.OpenGroup -> {
is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START)
is HomeActivitySharedAction.OpenGroup -> {
views.drawerLayout.closeDrawer(GravityCompat.START)
// Temporary
@ -198,10 +197,10 @@ class HomeActivity :
// we might want to delay that to avoid having the drawer animation lagging
// would be probably better to let the drawer do that? in the on closed callback?
}
is HomeActivitySharedAction.OpenSpacePreview -> {
is HomeActivitySharedAction.OpenSpacePreview -> {
startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
}
is HomeActivitySharedAction.AddSpace -> {
is HomeActivitySharedAction.AddSpace -> {
createSpaceResultLauncher.launch(SpaceCreationActivity.newIntent(this))
}
is HomeActivitySharedAction.ShowSpaceSettings -> {
@ -214,11 +213,11 @@ class HomeActivity :
})
.show(supportFragmentManager, "SPACE_SETTINGS")
}
is HomeActivitySharedAction.OpenSpaceInvite -> {
is HomeActivitySharedAction.OpenSpaceInvite -> {
SpaceInviteBottomSheet.newInstance(sharedAction.spaceId)
.show(supportFragmentManager, "SPACE_INVITE")
}
HomeActivitySharedAction.SendSpaceFeedBack -> {
HomeActivitySharedAction.SendSpaceFeedBack -> {
bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
}
}.exhaustive
@ -234,9 +233,9 @@ class HomeActivity :
homeActivityViewModel.observeViewEvents {
when (it) {
is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it)
is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it)
HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush()
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it)
HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush()
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
}.exhaustive
}
homeActivityViewModel.subscribe(this) { renderState(it) }
@ -244,6 +243,21 @@ class HomeActivity :
shortcutsHandler.observeRoomsAndBuildShortcuts()
.disposeOnDestroy()
if (!vectorPreferences.didPromoteNewRestrictedFeature()) {
promoteRestrictedViewModel.subscribe(this) {
if (it.activeSpaceSummary != null && !it.activeSpaceSummary.isPublic
&& it.activeSpaceSummary.otherMemberIds.isNotEmpty()) {
// It's a private space with some members show this once
if (it.canUserManageSpace && !popupAlertManager.hasAlertsToShow()) {
if (!vectorPreferences.didPromoteNewRestrictedFeature()) {
vectorPreferences.setDidPromoteNewRestrictedFeature()
RestrictedPromoBottomSheet().show(supportFragmentManager, "RestrictedPromoBottomSheet")
}
}
}
}
}
if (isFirstCreation()) {
handleIntent(intent)
}
@ -289,7 +303,7 @@ class HomeActivity :
private fun renderState(state: HomeActivityViewState) {
when (val status = state.initialSyncProgressServiceStatus) {
is InitialSyncProgressService.Status.Idle -> {
is InitialSyncProgressService.Status.Idle -> {
views.waitingView.root.isVisible = false
}
is InitialSyncProgressService.Status.Progressing -> {
@ -453,15 +467,15 @@ class HomeActivity :
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_home_suggestion -> {
R.id.menu_home_suggestion -> {
bugReporter.openBugReportScreen(this, ReportType.SUGGESTION)
return true
}
R.id.menu_home_report_bug -> {
R.id.menu_home_report_bug -> {
bugReporter.openBugReportScreen(this, ReportType.BUG_REPORT)
return true
}
R.id.menu_home_init_sync_legacy -> {
R.id.menu_home_init_sync_legacy -> {
// Configure the SDK
initialSyncStrategy = InitialSyncStrategy.Legacy
// And clear cache
@ -475,11 +489,11 @@ class HomeActivity :
MainActivity.restartApp(this, MainActivityArgs(clearCache = true))
return true
}
R.id.menu_home_filter -> {
R.id.menu_home_filter -> {
navigator.openRoomsFiltering(this)
return true
}
R.id.menu_home_setting -> {
R.id.menu_home_setting -> {
navigator.openSettings(this)
return true
}
@ -550,4 +564,6 @@ class HomeActivity :
private const val ROOM_LINK_PREFIX = "${MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/"
private const val USER_LINK_PREFIX = "${MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/"
}
override fun create(initialState: ActiveSpaceViewState) = promoteRestrictedViewModelFactory.create(initialState)
}

View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
data class ActiveSpaceViewState(
val isInSpaceMode: Boolean = false,
val activeSpaceSummary: RoomSummary? = null,
val canUserManageSpace: Boolean = false
) : MvRxState
class PromoteRestrictedViewModel @AssistedInject constructor(
@Assisted initialState: ActiveSpaceViewState,
private val activeSessionHolder: ActiveSessionHolder,
appStateHandler: AppStateHandler
) : VectorViewModel<ActiveSpaceViewState, EmptyAction, EmptyViewEvents>(initialState) {
init {
appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().execute { state ->
val groupingMethod = state.invoke()?.orNull()
val isSpaceMode = groupingMethod is RoomGroupingMethod.BySpace
val currentSpace = (groupingMethod as? RoomGroupingMethod.BySpace)?.spaceSummary
val canManage = currentSpace?.roomId?.let { roomId ->
activeSessionHolder.getSafeActiveSession()
?.getRoom(roomId)
?.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
?.content?.toModel<PowerLevelsContent>()?.let {
PowerLevelsHelper(it).isUserAllowedToSend(activeSessionHolder.getActiveSession().myUserId, true, EventType.STATE_SPACE_CHILD)
} ?: false
} ?: false
copy(
isInSpaceMode = isSpaceMode,
activeSpaceSummary = currentSpace,
canUserManageSpace = canManage
)
}
}
@AssistedFactory
interface Factory {
fun create(initialState: ActiveSpaceViewState): PromoteRestrictedViewModel
}
companion object : MvRxViewModelFactory<PromoteRestrictedViewModel, ActiveSpaceViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: ActiveSpaceViewState): PromoteRestrictedViewModel? {
val factory = when (viewModelContext) {
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity as? Factory
}
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
}
}
override fun handle(action: EmptyAction) {}
}

View file

@ -72,7 +72,7 @@ class MigrateRoomBottomSheet :
if (state.migrationReason == MigrationReason.MANUAL) {
views.descriptionText.text = getString(R.string.upgrade_room_warning)
views.upgradeFromTo.text = getString(R.string.upgrade_public_room_from_to, state.currentVersion, state.newVersion)
views.upgradeFromTo.text = getString(R.string.upgrade_public_room_from_to_version, state.currentVersion, state.newVersion)
} else if (state.migrationReason == MigrationReason.FOR_RESTRICTED) {
views.descriptionText.setTextOrHide(state.customDescription)
views.upgradeFromTo.text = getString(R.string.upgrade_room_for_restricted_note)

View file

@ -52,6 +52,10 @@ class PopupAlertManager @Inject constructor() {
private val alertQueue = mutableListOf<VectorAlert>()
fun hasAlertsToShow() : Boolean {
return currentAlerter != null || alertQueue.isNotEmpty()
}
fun postVectorAlert(alert: VectorAlert) {
synchronized(alertQueue) {
alertQueue.add(alert)

View file

@ -188,6 +188,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
private const val SETTINGS_DISPLAY_ALL_EVENTS_KEY = "SETTINGS_DISPLAY_ALL_EVENTS_KEY"
private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH"
private const val DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE = "DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE"
private const val MEDIA_SAVING_3_DAYS = 0
private const val MEDIA_SAVING_1_WEEK = 1
@ -345,6 +346,16 @@ class VectorPreferences @Inject constructor(private val context: Context) {
}
}
fun didPromoteNewRestrictedFeature(): Boolean {
return defaultPrefs.getBoolean(DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE, false)
}
fun setDidPromoteNewRestrictedFeature() {
defaultPrefs.edit {
putBoolean(DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE, true)
}
}
/**
* Tells if we have already asked the user to disable battery optimisations on android >= M devices.
*

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.spaces
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.databinding.BottomSheetSpaceAdvertiseRestrictedBinding
class RestrictedPromoBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetSpaceAdvertiseRestrictedBinding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
BottomSheetSpaceAdvertiseRestrictedBinding.inflate(inflater, container, false)
override val showExpanded = true
var learnMoreMode: Boolean = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
render()
views.skipButton.debouncedClicks {
dismiss()
}
views.learnMore.debouncedClicks {
if (learnMoreMode) {
dismiss()
} else {
learnMoreMode = true
render()
}
}
}
private fun render() {
if (learnMoreMode) {
views.title.text = getString(R.string.new_let_people_in_spaces_find_and_join)
views.topDescription.text = getString(R.string.to_help_space_members_find_and_join)
views.imageHint.isVisible = true
views.bottomDescription.isVisible = true
views.bottomDescription.text = getString(R.string.this_makes_it_easy_for_rooms_to_stay_private_to_a_space)
views.skipButton.isVisible = false
views.learnMore.text = getString(R.string.ok)
} else {
views.title.text = getString(R.string.help_space_members)
views.topDescription.text = getString(R.string.help_people_in_spaces_find_and_join)
views.imageHint.isVisible = false
views.bottomDescription.isVisible = false
views.skipButton.isVisible = true
views.learnMore.text = getString(R.string.learn_more)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://www.figma.com/file/HOGxCoUWoedha639SjD90n/%5BBeta%5D-Restricted-room-access?node-id=107%3A61157 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorSurface"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="16dp"
android:layout_marginBottom="12dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_beta_pill" />
<TextView
style="@style/Widget.Vector.TextView.Title"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="12dp"
android:gravity="start"
tools:text="@string/new_let_people_in_spaces_find_and_join"
android:textColor="?vctr_content_primary"
android:textStyle="bold" />
<TextView
style="@style/Widget.Vector.TextView.Body"
android:id="@+id/topDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:gravity="start"
android:textColor="?vctr_content_secondary"
tools:text="@string/help_people_in_spaces_find_and_join" />
<ImageView
android:id="@+id/imageHint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:maxWidth="300dp"
android:src="@drawable/room_settings"
android:visibility="gone"
tools:visibility="visible" />
<TextView
style="@style/Widget.Vector.TextView.Body"
android:id="@+id/bottomDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="start"
android:text="@string/this_makes_it_easy_for_rooms_to_stay_private_to_a_space"
android:textColor="?vctr_content_secondary"
android:visibility="gone"
tools:visibility="visible" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:gravity="end"
android:orientation="horizontal">
<Button
android:id="@+id/skipButton"
style="@style/Widget.Vector.Button.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/skip"
android:textAllCaps="true" />
<Button
android:id="@+id/learnMore"
style="@style/Widget.Vector.Button.Positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/learn_more"
android:textAllCaps="true" />
</LinearLayout>
</LinearLayout>

View file

@ -385,6 +385,7 @@
<string name="dismiss">Dismiss</string>
<string name="reset">Reset</string>
<string name="start_chatting">Start Chatting</string>
<string name="learn_more">Learn More</string>
<string name="spaces">Spaces</string>
<!-- Permissions denied forever -->
@ -3504,6 +3505,7 @@
<string name="upgrade_private_room">Upgrade private room</string>
<string name="upgrade_room_warning">Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.\nThis usually only affects how the room is processed on the server.</string>
<string name="upgrade_public_room_from_to">You\'ll upgrade this room from %s to %s.</string>
<string name="upgrade_public_room_from_to_version">You\'ll upgrade this room from %1$s to %2$s.</string>
<string name="upgrade_room_auto_invite">Automatically invite users</string>
<string name="upgrade_room_update_parent_space">Automatically update space parent</string>
<string name="upgrade_room_no_power_to_manage">You need permission to upgrade a room</string>
@ -3535,4 +3537,10 @@
<string name="upgrade_room_for_restricted_no_param">Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. Youll be able to change this in room settings anytime.</string>
<string name="upgrade_room_for_restricted_note">Please note upgrading will make a new version of the room. All current messages will stay in this archived room.</string>
<string name="new_let_people_in_spaces_find_and_join">New: Let people in spaces find and join private rooms</string>
<string name="help_people_in_spaces_find_and_join">Help people in spaces to find and join private rooms themselves, no need to manually invite everyone.</string>
<string name="this_makes_it_easy_for_rooms_to_stay_private_to_a_space">This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.</string>
<string name="help_space_members">Help space members find private rooms</string>
<string name="to_help_space_members_find_and_join">To help space members find and join a private room, go to that rooms settings by tapping on the avatar.</string>
</resources>