diff --git a/changelog.d/3631.feature b/changelog.d/3631.feature new file mode 100644 index 0000000000..01e1ee28cc --- /dev/null +++ b/changelog.d/3631.feature @@ -0,0 +1 @@ +Restricted Join Rule | Inform admins of new option \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt index 1fe4f9d90a..b36d05b6c0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt @@ -84,7 +84,7 @@ internal data class RoomVersions( * } * } */ - @Json(name = "room_capabilities") + @Json(name = "org.matrix.msc3244.room_capabilities") val roomCapabilities: JsonDict? = null ) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 9ae6d4c39f..a302276f45 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -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) } diff --git a/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt new file mode 100644 index 0000000000..ae7b495aa2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt @@ -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(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()?.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 { + + @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) {} +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomBottomSheet.kt index 2fa210a748..6e2a60c94f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomBottomSheet.kt @@ -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) diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt index a982858ffd..9842709f9e 100644 --- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt @@ -52,6 +52,10 @@ class PopupAlertManager @Inject constructor() { private val alertQueue = mutableListOf() + fun hasAlertsToShow() : Boolean { + return currentAlerter != null || alertQueue.isNotEmpty() + } + fun postVectorAlert(alert: VectorAlert) { synchronized(alertQueue) { alertQueue.add(alert) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 77aa624e0e..259c3662fc 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -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. * diff --git a/vector/src/main/java/im/vector/app/features/spaces/RestrictedPromoBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/RestrictedPromoBottomSheet.kt new file mode 100644 index 0000000000..dbea6807ce --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/RestrictedPromoBottomSheet.kt @@ -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() { + + 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) + } + } +} diff --git a/vector/src/main/res/drawable-nodpi/room_settings.png b/vector/src/main/res/drawable-nodpi/room_settings.png new file mode 100644 index 0000000000..2e3fb404fa Binary files /dev/null and b/vector/src/main/res/drawable-nodpi/room_settings.png differ diff --git a/vector/src/main/res/layout/bottom_sheet_space_advertise_restricted.xml b/vector/src/main/res/layout/bottom_sheet_space_advertise_restricted.xml new file mode 100644 index 0000000000..b0f8c3893d --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_space_advertise_restricted.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + +