From ff4bbf0a8a16d35847e9c59aeba1b481e330a0cd Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 28 Sep 2021 13:15:51 +0200 Subject: [PATCH 01/72] Add "Create room" shortcut in Explore Space screen --- changelog.d/3932.bugfix | 1 + .../features/navigation/DefaultNavigator.kt | 4 +- .../app/features/navigation/Navigator.kt | 2 +- .../RoomDirectorySharedAction.kt | 1 + .../createroom/CreateRoomActivity.kt | 35 +++++-- .../createroom/CreateRoomFragment.kt | 22 +++-- .../createroom/CreateRoomViewModel.kt | 19 ++-- .../createroom/CreateRoomViewState.kt | 6 +- .../features/spaces/SpaceExploreActivity.kt | 22 +++++ .../spaces/explore/SpaceDirectoryFragment.kt | 10 ++ .../explore/SpaceDirectoryViewAction.kt | 2 + .../explore/SpaceDirectoryViewEvents.kt | 1 + .../spaces/explore/SpaceDirectoryViewModel.kt | 97 ++++++++++++++++++- .../SpaceAddRoomSpaceChooserBottomSheet.kt | 8 ++ ...tom_sheet_add_rooms_or_spaces_to_space.xml | 32 +++--- .../res/layout/fragment_space_directory.xml | 14 +++ 16 files changed, 231 insertions(+), 45 deletions(-) create mode 100644 changelog.d/3932.bugfix diff --git a/changelog.d/3932.bugfix b/changelog.d/3932.bugfix new file mode 100644 index 0000000000..76e90f2bfd --- /dev/null +++ b/changelog.d/3932.bugfix @@ -0,0 +1 @@ +Explore Rooms overflow menu - content update include "Create room" \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 30ead8a6bf..c219d7feff 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -309,8 +309,8 @@ class DefaultNavigator @Inject constructor( } } - override fun openCreateRoom(context: Context, initialName: String) { - val intent = CreateRoomActivity.getIntent(context, initialName) + override fun openCreateRoom(context: Context, initialName: String, openAfterCreate: Boolean) { + val intent = CreateRoomActivity.getIntent(context = context, initialName = initialName, openAfterCreate = openAfterCreate) context.startActivity(intent) } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 6778c39a22..2f152b649f 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -76,7 +76,7 @@ interface Navigator { fun openMatrixToBottomSheet(context: Context, link: String) - fun openCreateRoom(context: Context, initialName: String = "") + fun openCreateRoom(context: Context, initialName: String = "", openAfterCreate: Boolean = true) fun openCreateDirectRoom(context: Context) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectorySharedAction.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectorySharedAction.kt index 9911ce6686..ea9211cc7b 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectorySharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectorySharedAction.kt @@ -25,5 +25,6 @@ sealed class RoomDirectorySharedAction : VectorSharedAction { object Back : RoomDirectorySharedAction() object CreateRoom : RoomDirectorySharedAction() object Close : RoomDirectorySharedAction() + data class CreateRoomSuccess(val createdRoomId: String) : RoomDirectorySharedAction() object ChangeProtocol : RoomDirectorySharedAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt index e9762d09d3..058bd97fbd 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt @@ -16,10 +16,12 @@ package im.vector.app.features.roomdirectory.createroom +import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle import androidx.lifecycle.lifecycleScope +import com.airbnb.mvrx.Mavericks import com.google.android.material.appbar.MaterialToolbar import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment @@ -49,13 +51,11 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarC override fun initUiAndData() { if (isFirstCreation()) { + val fragmentArgs: CreateRoomArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return addFragment( views.simpleFragmentContainer, CreateRoomFragment::class.java, - CreateRoomArgs( - intent?.getStringExtra(INITIAL_NAME) ?: "", - isSpace = intent?.getBooleanExtra(IS_SPACE, false) ?: false - ) + fragmentArgs ) } } @@ -68,20 +68,35 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarC .onEach { sharedAction -> when (sharedAction) { is RoomDirectorySharedAction.Back, - is RoomDirectorySharedAction.Close -> finish() + is RoomDirectorySharedAction.Close -> finish() + is RoomDirectorySharedAction.CreateRoomSuccess -> { + setResult(Activity.RESULT_OK, Intent().apply { putExtra(RESULT_CREATED_ROOM_ID, sharedAction.createdRoomId) }) + finish() + } + else -> { + // nop + } } } .launchIn(lifecycleScope) } companion object { - private const val INITIAL_NAME = "INITIAL_NAME" - private const val IS_SPACE = "IS_SPACE" - fun getIntent(context: Context, initialName: String = "", isSpace: Boolean = false): Intent { + const val RESULT_CREATED_ROOM_ID = "RESULT_CREATED_ROOM_ID" + + fun getIntent(context: Context, + initialName: String = "", + isSpace: Boolean = false, + openAfterCreate: Boolean = true, + currentSpaceId: String? = null): Intent { return Intent(context, CreateRoomActivity::class.java).apply { - putExtra(INITIAL_NAME, initialName) - putExtra(IS_SPACE, isSpace) + putExtra(Mavericks.KEY_ARG, CreateRoomArgs( + initialName = initialName, + isSpace = isSpace, + openAfterCreate = openAfterCreate, + parentSpaceId = currentSpaceId + )) } } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index 1244a0f64e..c14c6be2e0 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -56,7 +56,8 @@ import javax.inject.Inject data class CreateRoomArgs( val initialName: String, val parentSpaceId: String? = null, - val isSpace: Boolean = false + val isSpace: Boolean = false, + val openAfterCreate: Boolean = true ) : Parcelable class CreateRoomFragment @Inject constructor( @@ -226,16 +227,19 @@ class CreateRoomFragment @Inject constructor( views.waitingView.root.isVisible = async is Loading if (async is Success) { // Navigate to freshly created room - if (state.isSubSpace) { - navigator.switchToSpace( - requireContext(), - async(), - Navigator.PostSwitchSpaceAction.None - ) - } else { - navigator.openRoom(requireActivity(), async()) + if (state.openAfterCreate) { + if (state.isSubSpace) { + navigator.switchToSpace( + requireContext(), + async(), + Navigator.PostSwitchSpaceAction.None + ) + } else { + navigator.openRoom(requireActivity(), async()) + } } + sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoomSuccess(async())) sharedActionViewModel.post(RoomDirectorySharedAction.Close) } else { // Populate list with Epoxy diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt index e0ffdc7a52..34f51b92a8 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -27,6 +27,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.AppStateHandler import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.raw.wellknown.getElementWellknown @@ -52,10 +53,11 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset import timber.log.Timber -class CreateRoomViewModel @AssistedInject constructor(@Assisted private val initialState: CreateRoomViewState, +class CreateRoomViewModel @AssistedInject constructor(@Assisted val initialState: CreateRoomViewState, private val session: Session, private val rawService: RawService, - vectorPreferences: VectorPreferences + private val vectorPreferences: VectorPreferences, + appStateHandler: AppStateHandler ) : VectorViewModel(initialState) { @AssistedFactory @@ -69,6 +71,8 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init initHomeServerName() initAdminE2eByDefault() + val parentSpaceId = initialState.parentSpaceId ?: appStateHandler.safeActiveSpaceId() + val restrictedSupport = session.getHomeServerCapabilities().isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED) val createRestricted = when (restrictedSupport) { HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED -> true @@ -76,7 +80,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init else -> false } - val defaultJoinRules = if (initialState.parentSpaceId != null && createRestricted) { + val defaultJoinRules = if (parentSpaceId != null && createRestricted) { RoomJoinRules.RESTRICTED } else { RoomJoinRules.INVITE @@ -84,9 +88,10 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init setState { copy( + parentSpaceId = parentSpaceId, supportsRestricted = createRestricted, roomJoinRules = defaultJoinRules, - parentSpaceSummary = initialState.parentSpaceId?.let { session.getRoomSummary(it) } + parentSpaceSummary = parentSpaceId?.let { session.getRoomSummary(it) } ) } } @@ -162,7 +167,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init CreateRoomViewState( isEncrypted = adminE2EByDefault, hsAdminHasDisabledE2E = !adminE2EByDefault, - parentSpaceId = initialState.parentSpaceId + parentSpaceId = this.parentSpaceId ) } @@ -298,11 +303,11 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init runCatching { session.createRoom(createRoomParams) }.fold( { roomId -> - if (initialState.parentSpaceId != null) { + if (state.parentSpaceId != null) { // add it as a child try { session.spaceService() - .getSpace(initialState.parentSpaceId) + .getSpace(state.parentSpaceId) ?.addChildren(roomId, viaServers = null, order = null) } catch (failure: Throwable) { Timber.w(failure, "Failed to add as a child") diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt index 389d365875..cf8cc669ab 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt @@ -39,13 +39,15 @@ data class CreateRoomViewState( val parentSpaceSummary: RoomSummary? = null, val supportsRestricted: Boolean = false, val aliasLocalPart: String? = null, - val isSubSpace: Boolean = false + val isSubSpace: Boolean = false, + val openAfterCreate: Boolean = true ) : MavericksState { constructor(args: CreateRoomArgs) : this( roomName = args.initialName, parentSpaceId = args.parentSpaceId, - isSubSpace = args.isSpace + isSubSpace = args.isSpace, + openAfterCreate = args.openAfterCreate ) /** diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt index 3361305c83..ead1736d3c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt @@ -16,6 +16,7 @@ package im.vector.app.features.spaces +import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle @@ -25,13 +26,18 @@ import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R +import im.vector.app.core.extensions.commitTransaction +import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.matrixto.MatrixToBottomSheet +import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity import im.vector.app.features.navigation.Navigator import im.vector.app.features.spaces.explore.SpaceDirectoryArgs import im.vector.app.features.spaces.explore.SpaceDirectoryFragment +import im.vector.app.features.spaces.explore.SpaceDirectoryState +import im.vector.app.features.spaces.explore.SpaceDirectoryViewAction import im.vector.app.features.spaces.explore.SpaceDirectoryViewEvents import im.vector.app.features.spaces.explore.SpaceDirectoryViewModel @@ -44,6 +50,15 @@ class SpaceExploreActivity : VectorBaseActivity(), Matrix val sharedViewModel: SpaceDirectoryViewModel by viewModel() + private val createRoomResultLauncher = registerStartForActivityResult { activityResult -> + if (activityResult.resultCode == Activity.RESULT_OK) { + activityResult.data?.extras?.getString(CreateRoomActivity.RESULT_CREATED_ROOM_ID)?.let { + // we want to refresh from API + sharedViewModel.handle(SpaceDirectoryViewAction.RefreshUntilFound(it)) + } + } + } + private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() { override fun onFragmentResumed(fm: FragmentManager, f: Fragment) { if (f is MatrixToBottomSheet) { @@ -84,6 +99,13 @@ class SpaceExploreActivity : VectorBaseActivity(), Matrix is SpaceDirectoryViewEvents.NavigateToMxToBottomSheet -> { MatrixToBottomSheet.withLink(it.link).show(supportFragmentManager, "ShowChild") } + is SpaceDirectoryViewEvents.NavigateToCreateNewRoom -> { + createRoomResultLauncher.launch(CreateRoomActivity.getIntent( + this, + openAfterCreate = false, + currentSpaceId = it.currentSpaceId + )) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index f630323790..14850ebaa6 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -89,6 +89,9 @@ class SpaceDirectoryFragment @Inject constructor( SpaceAddRoomSpaceChooserBottomSheet.ACTION_ADD_SPACES -> { addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRoomsOnlySpaces)) } + SpaceAddRoomSpaceChooserBottomSheet.ACTION_CREATE_ROOM -> { + viewModel.handle(SpaceDirectoryViewAction.CreateNewRoom) + } else -> { // nop } @@ -114,6 +117,12 @@ class SpaceDirectoryFragment @Inject constructor( invalidateOptionsMenu() } + views.addOrCreateChatRoomButton.debouncedClicks { + withState(viewModel) { + addExistingRooms(it.spaceId) + } + } + views.spaceCard.matrixToCardMainButton.isVisible = false views.spaceCard.matrixToCardSecondaryButton.isVisible = false } @@ -142,6 +151,7 @@ class SpaceDirectoryFragment @Inject constructor( } spaceCardRenderer.render(state.currentRootSummary, emptyList(), this, views.spaceCard) + views.addOrCreateChatRoomButton.isVisible = state.canAddRooms } override fun onPrepareOptionsMenu(menu: Menu) = withState(viewModel) { state -> diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt index 3ced017d61..2166a7e306 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewAction.kt @@ -24,7 +24,9 @@ sealed class SpaceDirectoryViewAction : VectorViewModelAction { data class JoinOrOpen(val spaceChildInfo: SpaceChildInfo) : SpaceDirectoryViewAction() data class ShowDetails(val spaceChildInfo: SpaceChildInfo) : SpaceDirectoryViewAction() data class NavigateToRoom(val roomId: String) : SpaceDirectoryViewAction() + object CreateNewRoom : SpaceDirectoryViewAction() object HandleBack : SpaceDirectoryViewAction() object Retry : SpaceDirectoryViewAction() + data class RefreshUntilFound(val roomIdToFind: String) : SpaceDirectoryViewAction() object LoadAdditionalItemsIfNeeded : SpaceDirectoryViewAction() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewEvents.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewEvents.kt index 3ac0426de9..6359eff68d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewEvents.kt @@ -22,4 +22,5 @@ sealed class SpaceDirectoryViewEvents : VectorViewEvents { object Dismiss : SpaceDirectoryViewEvents() data class NavigateToRoom(val roomId: String) : SpaceDirectoryViewEvents() data class NavigateToMxToBottomSheet(val link: String) : SpaceDirectoryViewEvents() + data class NavigateToCreateNewRoom(val currentSpaceId: String) : SpaceDirectoryViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index d7bdf4f511..b1dafbccfe 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -71,6 +71,27 @@ class SpaceDirectoryViewModel @AssistedInject constructor( observeJoinedRooms() observeMembershipChanges() observePermissions() + observeKnownSummaries() + } + + private fun observeKnownSummaries() { + // A we prefer to use known summaries to have better name resolution + // it's important to have them up to date. Particularly after creation where + // resolved name is sometimes just "New Room" + session.rx().liveRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + includeType = null + } + ).execute { + val updatedRoomSummaries = it + copy( + knownRoomSummaries = this.knownRoomSummaries.map { rs -> + updatedRoomSummaries.invoke()?.firstOrNull { it.roomId == rs.roomId } + ?: rs + } + ) + } } private fun observePermissions() { @@ -103,7 +124,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor( try { val query = session.spaceService().querySpaceChildren( spaceId, - limit = 10 + limit = PAGE_LENGTH ) val knownSummaries = query.children.mapNotNull { session.getRoomSummary(it.childRoomId) @@ -181,9 +202,17 @@ class SpaceDirectoryViewModel @AssistedInject constructor( SpaceDirectoryViewAction.Retry -> { handleRetry() } + is SpaceDirectoryViewAction.RefreshUntilFound -> { + handleRefreshUntilFound(action.roomIdToFind) + } SpaceDirectoryViewAction.LoadAdditionalItemsIfNeeded -> { loadAdditionalItemsIfNeeded() } + is SpaceDirectoryViewAction.CreateNewRoom -> { + withState { state -> + _viewEvents.post(SpaceDirectoryViewEvents.NavigateToCreateNewRoom(state.currentRootSummary?.roomId ?: initialState.spaceId)) + } + } } } @@ -207,6 +236,66 @@ class SpaceDirectoryViewModel @AssistedInject constructor( refreshFromApi(state.hierarchyStack.lastOrNull() ?: initialState.spaceId) } + private fun handleRefreshUntilFound(roomIdToFind: String?) = withState { state -> + val currentRootId = state.hierarchyStack.lastOrNull() ?: initialState.spaceId + + val mutablePaginationStatus = state.paginationStatus.toMutableMap().apply { + this[currentRootId] = Loading() + } + + // mark as paginating + setState { + copy( + paginationStatus = mutablePaginationStatus + ) + } + + viewModelScope.launch(Dispatchers.IO) { + var query = session.spaceService().querySpaceChildren( + currentRootId, + limit = PAGE_LENGTH + ) + + var knownSummaries = query.children.mapNotNull { + session.getRoomSummary(it.childRoomId) + ?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced) + }.distinctBy { it.roomId } + + while (!query.children.any { it.childRoomId == roomIdToFind } && query.nextToken != null) { + // continue to paginate until found + val paginate = session.spaceService().querySpaceChildren( + currentRootId, + limit = PAGE_LENGTH, + from = query.nextToken, + knownStateList = query.childrenState + ) + + knownSummaries = ( + knownSummaries + + (paginate.children.mapNotNull { + session.getRoomSummary(it.childRoomId) + ?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced) + }) + ).distinctBy { it.roomId } + + query = query.copy( + children = query.children + paginate.children, + nextToken = paginate.nextToken + ) + } + + setState { + copy( + apiResults = this.apiResults.toMutableMap().apply { + this[currentRootId] = Success(query) + }, + paginationStatus = this.paginationStatus.toMutableMap().apply { this[currentRootId] = Success(Unit) }.toMap(), + knownRoomSummaries = (state.knownRoomSummaries + knownSummaries).distinctBy { it.roomId }, + ) + } + } + } + private fun handleExploreSubSpace(action: SpaceDirectoryViewAction.ExploreSubSpace) = withState { state -> val newRootId = action.spaceChildInfo.childRoomId val curSum = RoomSummary( @@ -252,7 +341,9 @@ class SpaceDirectoryViewModel @AssistedInject constructor( if (mutablePaginationStatus[currentRootId] is Loading) return@withState setState { - copy(paginationStatus = mutablePaginationStatus.toMap()) + copy(paginationStatus = mutablePaginationStatus.apply { + this[currentRootId] = Loading() + }) } viewModelScope.launch(Dispatchers.IO) { @@ -268,7 +359,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor( } val query = session.spaceService().querySpaceChildren( currentRootId, - limit = 10, + limit = PAGE_LENGTH, from = currentResponse.nextToken, knownStateList = currentResponse.childrenState ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomSpaceChooserBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomSpaceChooserBottomSheet.kt index 971ff7e0b1..4ad7aab940 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomSpaceChooserBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomSpaceChooserBottomSheet.kt @@ -34,6 +34,13 @@ class SpaceAddRoomSpaceChooserBottomSheet : VectorBaseBottomSheetDialogFragment< override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + views.createRooms.views.bottomSheetActionClickableZone.debouncedClicks { + setFragmentResult(REQUEST_KEY, Bundle().apply { + putString(BUNDLE_KEY_ACTION, ACTION_CREATE_ROOM) + }) + dismiss() + } + views.addSpaces.views.bottomSheetActionClickableZone.debouncedClicks { setFragmentResult(REQUEST_KEY, Bundle().apply { putString(BUNDLE_KEY_ACTION, ACTION_ADD_SPACES) @@ -55,6 +62,7 @@ class SpaceAddRoomSpaceChooserBottomSheet : VectorBaseBottomSheetDialogFragment< const val BUNDLE_KEY_ACTION = "SpaceAddRoomSpaceChooserBottomSheet.Action" const val ACTION_ADD_ROOMS = "Action.AddRoom" const val ACTION_ADD_SPACES = "Action.AddSpaces" + const val ACTION_CREATE_ROOM = "Action.CreateRoom" fun newInstance(): SpaceAddRoomSpaceChooserBottomSheet { return SpaceAddRoomSpaceChooserBottomSheet() diff --git a/vector/src/main/res/layout/bottom_sheet_add_rooms_or_spaces_to_space.xml b/vector/src/main/res/layout/bottom_sheet_add_rooms_or_spaces_to_space.xml index 25c2d1c3e5..f17bcd16f4 100644 --- a/vector/src/main/res/layout/bottom_sheet_add_rooms_or_spaces_to_space.xml +++ b/vector/src/main/res/layout/bottom_sheet_add_rooms_or_spaces_to_space.xml @@ -7,24 +7,34 @@ android:background="?android:colorBackground" android:orientation="vertical"> - + + + + + + + + + + + + + + app:actionTitle="@string/create_new_room" + app:leftIcon="@drawable/ic_fab_add" + app:tint="?vctr_content_primary" + app:titleTextColor="?vctr_content_primary" + tools:actionDescription="" /> + + \ No newline at end of file From 2dbe2b5f309fdff559bdde69e71b3c6a9fdf5700 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 30 Sep 2021 09:58:50 +0200 Subject: [PATCH 02/72] show/hide fab on scroll --- .../spaces/explore/SpaceDirectoryFragment.kt | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index 14850ebaa6..2c7da43988 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -26,6 +26,7 @@ import android.view.ViewGroup import androidx.core.text.toSpannable import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState @@ -83,16 +84,16 @@ class SpaceDirectoryFragment @Inject constructor( bundle.getString(SpaceAddRoomSpaceChooserBottomSheet.BUNDLE_KEY_ACTION)?.let { action -> val spaceId = withState(viewModel) { it.spaceId } when (action) { - SpaceAddRoomSpaceChooserBottomSheet.ACTION_ADD_ROOMS -> { + SpaceAddRoomSpaceChooserBottomSheet.ACTION_ADD_ROOMS -> { addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRooms)) } - SpaceAddRoomSpaceChooserBottomSheet.ACTION_ADD_SPACES -> { + SpaceAddRoomSpaceChooserBottomSheet.ACTION_ADD_SPACES -> { addExistingRoomActivityResult.launch(SpaceManageActivity.newIntent(requireContext(), spaceId, ManageType.AddRoomsOnlySpaces)) } SpaceAddRoomSpaceChooserBottomSheet.ACTION_CREATE_ROOM -> { viewModel.handle(SpaceDirectoryViewAction.CreateNewRoom) } - else -> { + else -> { // nop } } @@ -125,6 +126,24 @@ class SpaceDirectoryFragment @Inject constructor( views.spaceCard.matrixToCardMainButton.isVisible = false views.spaceCard.matrixToCardSecondaryButton.isVisible = false + + // Hide FAB when list is scrolling + views.spaceDirectoryList.addOnScrollListener( + object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + views.addOrCreateChatRoomButton.removeCallbacks(showFabRunnable) + + when (newState) { + RecyclerView.SCROLL_STATE_IDLE -> { + views.addOrCreateChatRoomButton.postDelayed(showFabRunnable, 250) + } + RecyclerView.SCROLL_STATE_DRAGGING, + RecyclerView.SCROLL_STATE_SETTLING -> { + views.addOrCreateChatRoomButton.hide() + } + } + } + }) } override fun onDestroyView() { @@ -134,6 +153,12 @@ class SpaceDirectoryFragment @Inject constructor( super.onDestroyView() } + private val showFabRunnable = Runnable { + if (isAdded) { + views.addOrCreateChatRoomButton.show() + } + } + override fun invalidate() = withState(viewModel) { state -> epoxyController.setData(state) From 5c5f2766b6e09a98786ebf761ef1ef8cd81c358f Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 10 Jan 2022 17:17:35 +0100 Subject: [PATCH 03/72] post rebase fix --- .../app/features/spaces/explore/SpaceDirectoryViewModel.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index b1dafbccfe..0b357a900b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -55,7 +55,9 @@ class SpaceDirectoryViewModel @AssistedInject constructor( override fun create(initialState: SpaceDirectoryState): SpaceDirectoryViewModel } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { + private const val PAGE_LENGTH = 10 + } init { @@ -78,7 +80,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor( // A we prefer to use known summaries to have better name resolution // it's important to have them up to date. Particularly after creation where // resolved name is sometimes just "New Room" - session.rx().liveRoomSummaries( + session.flow().liveRoomSummaries( roomSummaryQueryParams { memberships = listOf(Membership.JOIN) includeType = null From 5cbb1d99c7c657f8801c777dbed5e137fa7dc97c Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 10 Jan 2022 17:38:35 +0100 Subject: [PATCH 04/72] Code review --- .../features/roomdirectory/createroom/CreateRoomActivity.kt | 6 +++++- .../roomdirectory/createroom/CreateRoomViewModel.kt | 6 +++--- .../im/vector/app/features/spaces/SpaceExploreActivity.kt | 6 ++---- .../app/features/spaces/explore/SpaceDirectoryViewModel.kt | 4 ++-- vector/src/main/res/layout/fragment_space_directory.xml | 4 +--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt index 058bd97fbd..88bead5244 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt @@ -83,7 +83,7 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarC companion object { - const val RESULT_CREATED_ROOM_ID = "RESULT_CREATED_ROOM_ID" + private const val RESULT_CREATED_ROOM_ID = "RESULT_CREATED_ROOM_ID" fun getIntent(context: Context, initialName: String = "", @@ -99,5 +99,9 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarC )) } } + + fun getCreatedRoomId(data: Intent?): String? { + return data?.extras?.getString(RESULT_CREATED_ROOM_ID) + } } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt index 34f51b92a8..cd2bcd9924 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -25,9 +25,9 @@ import com.airbnb.mvrx.Uninitialized import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.AppStateHandler import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.AppStateHandler import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.raw.wellknown.getElementWellknown @@ -53,10 +53,10 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset import timber.log.Timber -class CreateRoomViewModel @AssistedInject constructor(@Assisted val initialState: CreateRoomViewState, +class CreateRoomViewModel @AssistedInject constructor(@Assisted private val initialState: CreateRoomViewState, private val session: Session, private val rawService: RawService, - private val vectorPreferences: VectorPreferences, + vectorPreferences: VectorPreferences, appStateHandler: AppStateHandler ) : VectorViewModel(initialState) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt index ead1736d3c..f4610805bc 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt @@ -26,17 +26,15 @@ import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R -import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.matrixto.MatrixToBottomSheet -import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity import im.vector.app.features.navigation.Navigator +import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity import im.vector.app.features.spaces.explore.SpaceDirectoryArgs import im.vector.app.features.spaces.explore.SpaceDirectoryFragment -import im.vector.app.features.spaces.explore.SpaceDirectoryState import im.vector.app.features.spaces.explore.SpaceDirectoryViewAction import im.vector.app.features.spaces.explore.SpaceDirectoryViewEvents import im.vector.app.features.spaces.explore.SpaceDirectoryViewModel @@ -52,7 +50,7 @@ class SpaceExploreActivity : VectorBaseActivity(), Matrix private val createRoomResultLauncher = registerStartForActivityResult { activityResult -> if (activityResult.resultCode == Activity.RESULT_OK) { - activityResult.data?.extras?.getString(CreateRoomActivity.RESULT_CREATED_ROOM_ID)?.let { + CreateRoomActivity.getCreatedRoomId(activityResult.data)?.let { // we want to refresh from API sharedViewModel.handle(SpaceDirectoryViewAction.RefreshUntilFound(it)) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 0b357a900b..abc70ccbc1 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -273,8 +273,8 @@ class SpaceDirectoryViewModel @AssistedInject constructor( ) knownSummaries = ( - knownSummaries - + (paginate.children.mapNotNull { + knownSummaries + + (paginate.children.mapNotNull { session.getRoomSummary(it.childRoomId) ?.takeIf { it.membership == Membership.JOIN } // only take if joined because it will be up to date (synced) }) diff --git a/vector/src/main/res/layout/fragment_space_directory.xml b/vector/src/main/res/layout/fragment_space_directory.xml index c638ce25bd..bc77bb1474 100644 --- a/vector/src/main/res/layout/fragment_space_directory.xml +++ b/vector/src/main/res/layout/fragment_space_directory.xml @@ -60,9 +60,7 @@ android:layout_marginBottom="16dp " android:contentDescription="@string/a11y_create_room" android:scaleType="center" - app:maxImageSize="20dp" android:src="@drawable/ic_fab_add" - android:visibility="visible" - tools:visibility="visible" /> + app:maxImageSize="20dp" /> \ No newline at end of file From 7be01ab7ae677bb26dbb725e6f0b4de3e203decd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 10 Jan 2022 17:47:27 +0100 Subject: [PATCH 05/72] Avoid allowing null String for state_key. Should always be an empty String according to the Matrix specification. There is no functional change, just a change in the SDK API for clarity regarding the Matrix specs. --- .../crypto/encryption/EncryptionTest.kt | 2 +- .../sdk/api/session/room/state/StateService.kt | 5 ++++- .../sdk/internal/session/room/DefaultRoom.kt | 2 +- .../session/room/state/DefaultStateService.kt | 18 +++++++++--------- .../session/room/state/SendStateTask.kt | 4 ++-- .../features/devtools/RoomDevToolViewModel.kt | 6 +++--- .../composer/MessageComposerViewModel.kt | 4 ++-- .../RoomMemberProfileViewModel.kt | 2 +- .../permissions/RoomPermissionsViewModel.kt | 2 +- .../features/widgets/WidgetPostAPIHandler.kt | 2 +- 10 files changed, 25 insertions(+), 22 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt index 189fc405eb..060201d624 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt @@ -62,7 +62,7 @@ class EncryptionTest : InstrumentedTest { // Send an encryption Event as a State Event room.sendStateEvent( eventType = EventType.STATE_ROOM_ENCRYPTION, - stateKey = null, + stateKey = "", body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent() ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index 4d3f95233d..e9b0e4f676 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -68,8 +68,11 @@ interface StateService { /** * Send a state event to the room + * @param eventType The type of event to send. + * @param stateKey The state_key for the state to send. Can be an empty string. + * @param body The content object of the event; the fields in this object will vary depending on the type of event */ - suspend fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict) + suspend fun sendStateEvent(eventType: String, stateKey: String, body: JsonDict) /** * Get a state event of the room diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index cb4bcdb606..8d96bf8603 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -130,7 +130,7 @@ internal class DefaultRoom(override val roomId: String, else -> { val params = SendStateTask.Params( roomId = roomId, - stateKey = null, + stateKey = "", eventType = EventType.STATE_ROOM_ENCRYPTION, body = mapOf( "algorithm" to algorithm diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 4ec27976a2..417417f439 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -68,7 +68,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private override suspend fun sendStateEvent( eventType: String, - stateKey: String?, + stateKey: String, body: JsonDict ) { val params = SendStateTask.Params( @@ -92,7 +92,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_TOPIC, body = mapOf("topic" to topic), - stateKey = null + stateKey = "" ) } @@ -100,7 +100,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_NAME, body = mapOf("name" to name), - stateKey = null + stateKey = "" ) } @@ -117,7 +117,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private // Sort for the cleanup .sorted() ).toContent(), - stateKey = null + stateKey = "" ) } @@ -125,7 +125,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY, body = mapOf("history_visibility" to readability), - stateKey = null + stateKey = "" ) } @@ -142,14 +142,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_JOIN_RULES, body = body, - stateKey = null + stateKey = "" ) } if (guestAccess != null) { sendStateEvent( eventType = EventType.STATE_ROOM_GUEST_ACCESS, body = mapOf("guest_access" to guestAccess), - stateKey = null + stateKey = "" ) } } @@ -159,7 +159,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_AVATAR, body = mapOf("url" to response.contentUri), - stateKey = null + stateKey = "" ) } @@ -167,7 +167,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_AVATAR, body = emptyMap(), - stateKey = null + stateKey = "" ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt index 998e116a0e..56c69a05a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt @@ -26,7 +26,7 @@ import javax.inject.Inject internal interface SendStateTask : Task { data class Params( val roomId: String, - val stateKey: String?, + val stateKey: String, val eventType: String, val body: JsonDict ) @@ -39,7 +39,7 @@ internal class DefaultSendStateTask @Inject constructor( override suspend fun execute(params: SendStateTask.Params) { return executeRequest(globalErrorReceiver) { - if (params.stateKey == null) { + if (params.stateKey.isEmpty()) { roomAPI.sendStateEvent( roomId = params.roomId, stateEventType = params.eventType, diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt index 04d90a63e7..c3524e2cdf 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt @@ -174,8 +174,8 @@ class RoomDevToolViewModel @AssistedInject constructor( ?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content)) room.sendStateEvent( - state.selectedEvent?.type ?: "", - state.selectedEvent?.stateKey, + state.selectedEvent?.type.orEmpty(), + state.selectedEvent?.stateKey.orEmpty(), json ) @@ -213,7 +213,7 @@ class RoomDevToolViewModel @AssistedInject constructor( if (isState) { room.sendStateEvent( eventType, - state.sendEventDraft.stateKey, + state.sendEventDraft.stateKey.orEmpty(), json ) } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index a63a06928a..940099ddca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -552,7 +552,7 @@ class MessageComposerViewModel @AssistedInject constructor( ?: return launchSlashCommandFlowSuspendable { - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent) + room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent) } } @@ -619,7 +619,7 @@ class MessageComposerViewModel @AssistedInject constructor( private fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) { launchSlashCommandFlowSuspendable { - room.sendStateEvent(EventType.STATE_ROOM_AVATAR, null, RoomAvatarContent(changeAvatar.url).toContent()) + room.sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent()) } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 4f3d9d0776..63378a7edd 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -210,7 +210,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor( viewModelScope.launch { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) try { - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent) + room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent) _viewEvents.post(RoomMemberProfileViewEvents.OnSetPowerLevelSuccess) } catch (failure: Throwable) { _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt index 011c4ea8ae..7e8a66d12a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -124,7 +124,7 @@ class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialStat } ) } - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent.toContent()) + room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent.toContent()) setState { copy( isLoading = false diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index 99b3595d11..cdffbd5411 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -319,7 +319,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo launchWidgetAPIAction(widgetPostAPIMediator, eventData) { room.sendStateEvent( eventType = EventType.PLUMBING, - stateKey = null, + stateKey = "", body = params ) } From 3bdb05f7b466ffbccc17a04a08581f1095d3fbe1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 10 Jan 2022 17:49:47 +0100 Subject: [PATCH 06/72] Changelog --- changelog.d/4895.removal | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4895.removal diff --git a/changelog.d/4895.removal b/changelog.d/4895.removal new file mode 100644 index 0000000000..8b3e3adba4 --- /dev/null +++ b/changelog.d/4895.removal @@ -0,0 +1 @@ +`StateService.sendStateEvent()` now takes a non-nullable String for the parameter `stateKey`. If null was used, just now use an empty string. \ No newline at end of file From 7581a0b5493efa114b45647c60990f2551bc6f5f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 10 Jan 2022 18:36:04 +0100 Subject: [PATCH 07/72] Fix test compilation --- .../org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index 1c38edbbd9..6c91100fa5 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -542,7 +542,7 @@ class SpaceHierarchyTest : InstrumentedTest { ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value) ?.toContent() - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!) + room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!) it.countDown() } From 9ec662ccdc9be0768bf4403252df5d961b46a8b0 Mon Sep 17 00:00:00 2001 From: fedrunov Date: Tue, 11 Jan 2022 10:17:29 +0100 Subject: [PATCH 08/72] replace "kick" with "remove" --- changelog.d/4865.misc | 1 + .../session/room/members/MembershipService.kt | 2 +- .../membership/DefaultMembershipService.kt | 2 +- .../im/vector/app/features/command/Command.kt | 2 +- .../app/features/command/CommandParser.kt | 10 +++--- .../app/features/command/ParsedCommand.kt | 2 +- .../composer/MessageComposerViewModel.kt | 8 ++--- .../RoomMemberProfileViewModel.kt | 2 +- vector/src/main/res/values/strings.xml | 32 +++++++++---------- 9 files changed, 31 insertions(+), 30 deletions(-) create mode 100644 changelog.d/4865.misc diff --git a/changelog.d/4865.misc b/changelog.d/4865.misc new file mode 100644 index 0000000000..a253ab24c7 --- /dev/null +++ b/changelog.d/4865.misc @@ -0,0 +1 @@ +"/kick" command is replaced with "/remove" \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt index 198d6677a0..6dacd9cfd1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt @@ -77,7 +77,7 @@ interface MembershipService { /** * Kick a user from the room */ - suspend fun kick(userId: String, reason: String? = null) + suspend fun remove(userId: String, reason: String? = null) /** * Join the room, or accept an invitation. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index 6cf82dde44..49b58aa765 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -125,7 +125,7 @@ internal class DefaultMembershipService @AssistedInject constructor( membershipAdminTask.execute(params) } - override suspend fun kick(userId: String, reason: String?) { + override suspend fun remove(userId: String, reason: String?) { val params = MembershipAdminTask.Params(MembershipAdminTask.Type.KICK, roomId, userId, reason) membershipAdminTask.execute(params) } diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index 1950038691..c90d06a7b0 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -37,7 +37,7 @@ enum class Command(val command: String, val parameters: String, @StringRes val d JOIN_ROOM("/join", " [reason]", R.string.command_description_join_room, false), PART("/part", "[]", R.string.command_description_part_room, false), TOPIC("/topic", "", R.string.command_description_topic, false), - KICK_USER("/kick", " [reason]", R.string.command_description_kick_user, false), + REMOVE_USER("/remove", " [reason]", R.string.command_description_kick_user, false), CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick, false), CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", "", R.string.command_description_nick_for_room, false), ROOM_AVATAR("/roomavatar", "", R.string.command_description_room_avatar, true /* Since user has to know the mxc url */), diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index 4b2a4aa28c..22e1091c24 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -200,22 +200,22 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.INVITE) } } - Command.KICK_USER.command -> { + Command.REMOVE_USER.command -> { if (messageParts.size >= 2) { val userId = messageParts[1] if (MatrixPatterns.isUserId(userId)) { - ParsedCommand.KickUser( + ParsedCommand.RemoveUser( userId, - textMessage.substring(Command.KICK_USER.length + userId.length) + textMessage.substring(Command.REMOVE_USER.length + userId.length) .trim() .takeIf { it.isNotBlank() } ) } else { - ParsedCommand.ErrorSyntax(Command.KICK_USER) + ParsedCommand.ErrorSyntax(Command.REMOVE_USER) } } else { - ParsedCommand.ErrorSyntax(Command.KICK_USER) + ParsedCommand.ErrorSyntax(Command.REMOVE_USER) } } Command.BAN_USER.command -> { diff --git a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt index 4f8d19abb6..584272f3f4 100644 --- a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt @@ -51,7 +51,7 @@ sealed class ParsedCommand { class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand() class PartRoom(val roomAlias: String?) : ParsedCommand() class ChangeTopic(val topic: String) : ParsedCommand() - class KickUser(val userId: String, val reason: String?) : ParsedCommand() + class RemoveUser(val userId: String, val reason: String?) : ParsedCommand() class ChangeDisplayName(val displayName: String) : ParsedCommand() class ChangeDisplayNameForRoom(val displayName: String) : ParsedCommand() class ChangeRoomAvatar(val url: String) : ParsedCommand() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index a63a06928a..d787978aa9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -217,8 +217,8 @@ class MessageComposerViewModel @AssistedInject constructor( is ParsedCommand.UnignoreUser -> { handleUnignoreSlashCommand(slashCommandResult) } - is ParsedCommand.KickUser -> { - handleKickSlashCommand(slashCommandResult) + is ParsedCommand.RemoveUser -> { + handleRemoveSlashCommand(slashCommandResult) } is ParsedCommand.JoinRoom -> { handleJoinToAnotherRoomSlashCommand(slashCommandResult) @@ -576,9 +576,9 @@ class MessageComposerViewModel @AssistedInject constructor( } } - private fun handleKickSlashCommand(kick: ParsedCommand.KickUser) { + private fun handleRemoveSlashCommand(removeUser: ParsedCommand.RemoveUser) { launchSlashCommandFlowSuspendable { - room.kick(kick.userId, kick.reason) + room.remove(removeUser.userId, removeUser.reason) } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 4f3d9d0776..d6ba11d929 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -254,7 +254,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor( viewModelScope.launch { try { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) - room.kick(initialState.userId, action.reason) + room.remove(initialState.userId, action.reason) _viewEvents.post(RoomMemberProfileViewEvents.OnKickActionSuccess) } catch (failure: Throwable) { _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index dbdf104dff..11a3eb0a0d 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -25,8 +25,8 @@ You left the room %1$s rejected the invitation You rejected the invitation - %1$s kicked %2$s - You kicked %1$s + %1$s removed %2$s + You removed %1$s %1$s unbanned %2$s You unbanned %1$s %1$s banned %2$s @@ -229,8 +229,8 @@ You left. Reason: %1$s %1$s rejected the invitation. Reason: %2$s You rejected the invitation. Reason: %1$s - %1$s kicked %2$s. Reason: %3$s - You kicked %1$s. Reason: %2$s + %1$s removed %2$s. Reason: %3$s + You removed %1$s. Reason: %2$s %1$s unbanned %2$s. Reason: %3$s You unbanned %1$s. Reason: %2$s %1$s banned %2$s. Reason: %3$s @@ -884,7 +884,7 @@ Remove from this room Ban Unban - Kick + Remove from chat Reset to normal user Make moderator Make admin @@ -908,15 +908,15 @@ Cancel invite Are you sure you want to cancel the invite for this user? - Kick user - Reason to kick - kicking user will remove them from this room.\n\nTo prevent them from joining again, you should ban them instead. - kicking user will remove them from this space.\n\nTo prevent them from joining again, you should ban them instead. + Remove user + Reason to remove + The user will be removed from this room.\n\nTo prevent them from joining again, you should ban them instead. + The user will be removed from this space.\n\nTo prevent them from joining again, you should ban them instead. Ban user Reason to ban Unban user - Banning user will kick them from this room and prevent them from joining again. - Banning user will kick them from this space and prevent them from joining again. + Banning user will remove them from this room and prevent them from joining again. + Banning user will remove them from this space and prevent them from joining again. Unbanning user will allow them to join the room again. Unbanning user will allow them to join the space again. @@ -996,7 +996,7 @@ Send messages Invite users Change settings - Kick users + Remove users Ban users Remove messages sent by others Notify everyone @@ -1323,9 +1323,9 @@ Show room member state events Show chat effects Use /confetti command or send a message containing ❄️ or 🎉 - Includes invite/join/left/kick/ban events and avatar/display name changes. + Includes invite/join/left/remove/ban events and avatar/display name changes. Show join and leave events - Invites, kicks, and bans are unaffected. + Invites, removes, and bans are unaffected. Show account events Includes avatar and display name changes. Vibrate when mentioning a user @@ -1853,7 +1853,7 @@ Joins room with given address Leave room Set the room topic - Kicks user with given id + Removes user with given id from this room Changes your display nickname Changes your display nickname in the current room only Changes the avatar of the current room @@ -1903,7 +1903,7 @@ The community admin has not provided a long description for this community. - You have been kicked from %1$s by %2$s + You have been removed from %1$s by %2$s You have been banned from %1$s by %2$s Reason: %1$s Rejoin From 2aabbf0aa6ded0d97a08c13aece00d6019395de5 Mon Sep 17 00:00:00 2001 From: fedrunov Date: Tue, 11 Jan 2022 12:23:26 +0100 Subject: [PATCH 09/72] added aliases to commands --- .../command/AutocompleteCommandPresenter.kt | 2 +- .../im/vector/app/features/command/Command.kt | 78 +++--- .../app/features/command/CommandParser.kt | 222 ++++++++---------- .../home/room/detail/RoomDetailFragment.kt | 2 +- .../composer/MessageComposerViewModel.kt | 2 +- 5 files changed, 147 insertions(+), 159 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt index 5ad31aeaa6..9888f1e35e 100644 --- a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt +++ b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt @@ -50,7 +50,7 @@ class AutocompleteCommandPresenter @Inject constructor(context: Context, if (query.isNullOrEmpty()) { true } else { - it.command.startsWith(query, 1, true) + it.startsWith(query) } } controller.setData(data) diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index c90d06a7b0..4edd62a488 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -24,42 +24,46 @@ import im.vector.app.R * the user can write theses messages to perform some actions * the list will be displayed in this order */ -enum class Command(val command: String, val parameters: String, @StringRes val description: Int, val isDevCommand: Boolean) { - EMOTE("/me", "", R.string.command_description_emote, false), - BAN_USER("/ban", " [reason]", R.string.command_description_ban_user, false), - UNBAN_USER("/unban", " [reason]", R.string.command_description_unban_user, false), - IGNORE_USER("/ignore", " [reason]", R.string.command_description_ignore_user, false), - UNIGNORE_USER("/unignore", "", R.string.command_description_unignore_user, false), - SET_USER_POWER_LEVEL("/op", " []", R.string.command_description_op_user, false), - RESET_USER_POWER_LEVEL("/deop", "", R.string.command_description_deop_user, false), - ROOM_NAME("/roomname", "", R.string.command_description_room_name, false), - INVITE("/invite", " [reason]", R.string.command_description_invite_user, false), - JOIN_ROOM("/join", " [reason]", R.string.command_description_join_room, false), - PART("/part", "[]", R.string.command_description_part_room, false), - TOPIC("/topic", "", R.string.command_description_topic, false), - REMOVE_USER("/remove", " [reason]", R.string.command_description_kick_user, false), - CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick, false), - CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", "", R.string.command_description_nick_for_room, false), - ROOM_AVATAR("/roomavatar", "", R.string.command_description_room_avatar, true /* Since user has to know the mxc url */), - CHANGE_AVATAR_FOR_ROOM("/myroomavatar", "", R.string.command_description_avatar_for_room, true /* Since user has to know the mxc url */), - MARKDOWN("/markdown", "", R.string.command_description_markdown, false), - RAINBOW("/rainbow", "", R.string.command_description_rainbow, false), - RAINBOW_EMOTE("/rainbowme", "", R.string.command_description_rainbow_emote, false), - CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token, false), - SPOILER("/spoiler", "", R.string.command_description_spoiler, false), - SHRUG("/shrug", "", R.string.command_description_shrug, false), - LENNY("/lenny", "", R.string.command_description_lenny, false), - PLAIN("/plain", "", R.string.command_description_plain, false), - WHOIS("/whois", "", R.string.command_description_whois, false), - DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session, false), - CONFETTI("/confetti", "", R.string.command_confetti, false), - SNOWFALL("/snowfall", "", R.string.command_snow, false), - CREATE_SPACE("/createspace", " *", R.string.command_description_create_space, true), - ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_add_to_space, true), - JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true), - LEAVE_ROOM("/leave", "", R.string.command_description_leave_room, true), - UPGRADE_ROOM("/upgraderoom", "newVersion", R.string.command_description_upgrade_room, true); +enum class Command(val command: String, val aliases: Array?, val parameters: String, @StringRes val description: Int, val isDevCommand: Boolean) { + EMOTE("/me", null, "", R.string.command_description_emote, false), + BAN_USER("/ban", null, " [reason]", R.string.command_description_ban_user, false), + UNBAN_USER("/unban", null, " [reason]", R.string.command_description_unban_user, false), + IGNORE_USER("/ignore", null, " [reason]", R.string.command_description_ignore_user, false), + UNIGNORE_USER("/unignore", null, "", R.string.command_description_unignore_user, false), + SET_USER_POWER_LEVEL("/op", null, " []", R.string.command_description_op_user, false), + RESET_USER_POWER_LEVEL("/deop", null, "", R.string.command_description_deop_user, false), + ROOM_NAME("/roomname", null, "", R.string.command_description_room_name, false), + INVITE("/invite", null, " [reason]", R.string.command_description_invite_user, false), + JOIN_ROOM("/join", null, " [reason]", R.string.command_description_join_room, false), + PART("/part", null, "[]", R.string.command_description_part_room, false), + TOPIC("/topic", null, "", R.string.command_description_topic, false), + REMOVE_USER("/remove", arrayOf("/kick"), " [reason]", R.string.command_description_kick_user, false), + CHANGE_DISPLAY_NAME("/nick", null, "", R.string.command_description_nick, false), + CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", null, "", R.string.command_description_nick_for_room, false), + ROOM_AVATAR("/roomavatar", null, "", R.string.command_description_room_avatar, true /* Since user has to know the mxc url */), + CHANGE_AVATAR_FOR_ROOM("/myroomavatar", null, "", R.string.command_description_avatar_for_room, true /* Since user has to know the mxc url */), + MARKDOWN("/markdown", null, "", R.string.command_description_markdown, false), + RAINBOW("/rainbow", null, "", R.string.command_description_rainbow, false), + RAINBOW_EMOTE("/rainbowme", null, "", R.string.command_description_rainbow_emote, false), + CLEAR_SCALAR_TOKEN("/clear_scalar_token", null, "", R.string.command_description_clear_scalar_token, false), + SPOILER("/spoiler", null, "", R.string.command_description_spoiler, false), + SHRUG("/shrug", null, "", R.string.command_description_shrug, false), + LENNY("/lenny", null, "", R.string.command_description_lenny, false), + PLAIN("/plain", null, "", R.string.command_description_plain, false), + WHOIS("/whois", null, "", R.string.command_description_whois, false), + DISCARD_SESSION("/discardsession", null, "", R.string.command_description_discard_session, false), + CONFETTI("/confetti", null, "", R.string.command_confetti, false), + SNOWFALL("/snowfall", null, "", R.string.command_snow, false), + CREATE_SPACE("/createspace", null, " *", R.string.command_description_create_space, true), + ADD_TO_SPACE("/addToSpace", null, "spaceId", R.string.command_description_add_to_space, true), + JOIN_SPACE("/joinSpace", null, "spaceId", R.string.command_description_join_space, true), + LEAVE_ROOM("/leave", null, "", R.string.command_description_leave_room, true), + UPGRADE_ROOM("/upgraderoom", null, "newVersion", R.string.command_description_upgrade_room, true); - val length - get() = command.length + 1 + val allAliases = arrayOf(command, *aliases.orEmpty()) + + fun matches(inputCommand: CharSequence) = allAliases.any { it.contentEquals(inputCommand, true) } + + fun startsWith(input: CharSequence) = + allAliases.any { it.startsWith(input, 1, true) } } diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index 22e1091c24..c6de96859a 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -32,12 +32,12 @@ object CommandParser { * @param textMessage the text message * @return a parsed slash command (ok or error) */ - fun parseSplashCommand(textMessage: CharSequence): ParsedCommand { + fun parseSlashCommand(textMessage: CharSequence): ParsedCommand { // check if it has the Slash marker if (!textMessage.startsWith("/")) { return ParsedCommand.ErrorNotACommand } else { - Timber.v("parseSplashCommand") + Timber.v("parseSlashCommand") // "/" only if (textMessage.length == 1) { @@ -52,7 +52,7 @@ object CommandParser { val messageParts = try { textMessage.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() } } catch (e: Exception) { - Timber.e(e, "## manageSplashCommand() : split failed") + Timber.e(e, "## manageSlashCommand() : split failed") null } @@ -61,35 +61,32 @@ object CommandParser { return ParsedCommand.ErrorEmptySlashCommand } - return when (val slashCommand = messageParts.first()) { - Command.PLAIN.command -> { - val text = textMessage.substring(Command.PLAIN.command.length).trim() + val slashCommand = messageParts.first() + val message = textMessage.substring(slashCommand.length).trim() - if (text.isNotEmpty()) { - ParsedCommand.SendPlainText(text) + return when { + Command.PLAIN.matches(slashCommand) -> { + if (message.isNotEmpty()) { + ParsedCommand.SendPlainText(message = message) } else { ParsedCommand.ErrorSyntax(Command.PLAIN) } } - Command.CHANGE_DISPLAY_NAME.command -> { - val newDisplayName = textMessage.substring(Command.CHANGE_DISPLAY_NAME.command.length).trim() - - if (newDisplayName.isNotEmpty()) { - ParsedCommand.ChangeDisplayName(newDisplayName) + Command.CHANGE_DISPLAY_NAME.matches(slashCommand) -> { + if (message.isNotEmpty()) { + ParsedCommand.ChangeDisplayName(displayName = message) } else { ParsedCommand.ErrorSyntax(Command.CHANGE_DISPLAY_NAME) } } - Command.CHANGE_DISPLAY_NAME_FOR_ROOM.command -> { - val newDisplayName = textMessage.substring(Command.CHANGE_DISPLAY_NAME_FOR_ROOM.command.length).trim() - - if (newDisplayName.isNotEmpty()) { - ParsedCommand.ChangeDisplayNameForRoom(newDisplayName) + Command.CHANGE_DISPLAY_NAME_FOR_ROOM.matches(slashCommand) -> { + if (message.isNotEmpty()) { + ParsedCommand.ChangeDisplayNameForRoom(displayName = message) } else { ParsedCommand.ErrorSyntax(Command.CHANGE_DISPLAY_NAME_FOR_ROOM) } } - Command.ROOM_AVATAR.command -> { + Command.ROOM_AVATAR.matches(slashCommand) -> { if (messageParts.size == 2) { val url = messageParts[1] @@ -102,7 +99,7 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.ROOM_AVATAR) } } - Command.CHANGE_AVATAR_FOR_ROOM.command -> { + Command.CHANGE_AVATAR_FOR_ROOM.matches(slashCommand) -> { if (messageParts.size == 2) { val url = messageParts[1] @@ -115,40 +112,42 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.CHANGE_AVATAR_FOR_ROOM) } } - Command.TOPIC.command -> { - val newTopic = textMessage.substring(Command.TOPIC.command.length).trim() - - if (newTopic.isNotEmpty()) { - ParsedCommand.ChangeTopic(newTopic) + Command.TOPIC.matches(slashCommand) -> { + if (message.isNotEmpty()) { + ParsedCommand.ChangeTopic(topic = message) } else { ParsedCommand.ErrorSyntax(Command.TOPIC) } } - Command.EMOTE.command -> { - val message = textMessage.subSequence(Command.EMOTE.command.length, textMessage.length).trim() - - ParsedCommand.SendEmote(message) + Command.EMOTE.matches(slashCommand) -> { + if (message.isNotEmpty()) { + ParsedCommand.SendEmote(message) + } else { + ParsedCommand.ErrorSyntax(Command.EMOTE) + } } - Command.RAINBOW.command -> { - val message = textMessage.subSequence(Command.RAINBOW.command.length, textMessage.length).trim() - - ParsedCommand.SendRainbow(message) + Command.RAINBOW.matches(slashCommand) -> { + if (message.isNotEmpty()) { + ParsedCommand.SendRainbow(message) + } else { + ParsedCommand.ErrorSyntax(Command.RAINBOW) + } } - Command.RAINBOW_EMOTE.command -> { - val message = textMessage.subSequence(Command.RAINBOW_EMOTE.command.length, textMessage.length).trim() - - ParsedCommand.SendRainbowEmote(message) + Command.RAINBOW_EMOTE.matches(slashCommand) -> { + if (message.isNotEmpty()) { + ParsedCommand.SendRainbowEmote(message) + } else { + ParsedCommand.ErrorSyntax(Command.RAINBOW_EMOTE) + } } - Command.JOIN_ROOM.command -> { + Command.JOIN_ROOM.matches(slashCommand) -> { if (messageParts.size >= 2) { val roomAlias = messageParts[1] if (roomAlias.isNotEmpty()) { ParsedCommand.JoinRoom( roomAlias, - textMessage.substring(Command.JOIN_ROOM.length + roomAlias.length) - .trim() - .takeIf { it.isNotBlank() } + trimParts(textMessage, messageParts.take(2)) ) } else { ParsedCommand.ErrorSyntax(Command.JOIN_ROOM) @@ -157,23 +156,21 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.JOIN_ROOM) } } - Command.PART.command -> { + Command.PART.matches(slashCommand) -> { when (messageParts.size) { 1 -> ParsedCommand.PartRoom(null) 2 -> ParsedCommand.PartRoom(messageParts[1]) else -> ParsedCommand.ErrorSyntax(Command.PART) } } - Command.ROOM_NAME.command -> { - val newRoomName = textMessage.substring(Command.ROOM_NAME.command.length).trim() - - if (newRoomName.isNotEmpty()) { - ParsedCommand.ChangeRoomName(newRoomName) + Command.ROOM_NAME.matches(slashCommand) -> { + if (message.isNotEmpty()) { + ParsedCommand.ChangeRoomName(name = message) } else { ParsedCommand.ErrorSyntax(Command.ROOM_NAME) } } - Command.INVITE.command -> { + Command.INVITE.matches(slashCommand) -> { if (messageParts.size >= 2) { val userId = messageParts[1] @@ -181,9 +178,7 @@ object CommandParser { MatrixPatterns.isUserId(userId) -> { ParsedCommand.Invite( userId, - textMessage.substring(Command.INVITE.length + userId.length) - .trim() - .takeIf { it.isNotBlank() } + trimParts(textMessage, messageParts.take(2)) ) } userId.isEmail() -> { @@ -200,16 +195,14 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.INVITE) } } - Command.REMOVE_USER.command -> { + Command.REMOVE_USER.matches(slashCommand) -> { if (messageParts.size >= 2) { val userId = messageParts[1] if (MatrixPatterns.isUserId(userId)) { ParsedCommand.RemoveUser( userId, - textMessage.substring(Command.REMOVE_USER.length + userId.length) - .trim() - .takeIf { it.isNotBlank() } + trimParts(textMessage, messageParts.take(2)) ) } else { ParsedCommand.ErrorSyntax(Command.REMOVE_USER) @@ -218,16 +211,14 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.REMOVE_USER) } } - Command.BAN_USER.command -> { + Command.BAN_USER.matches(slashCommand) -> { if (messageParts.size >= 2) { val userId = messageParts[1] if (MatrixPatterns.isUserId(userId)) { ParsedCommand.BanUser( userId, - textMessage.substring(Command.BAN_USER.length + userId.length) - .trim() - .takeIf { it.isNotBlank() } + trimParts(textMessage, messageParts.take(2)) ) } else { ParsedCommand.ErrorSyntax(Command.BAN_USER) @@ -236,16 +227,14 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.BAN_USER) } } - Command.UNBAN_USER.command -> { + Command.UNBAN_USER.matches(slashCommand) -> { if (messageParts.size >= 2) { val userId = messageParts[1] if (MatrixPatterns.isUserId(userId)) { ParsedCommand.UnbanUser( userId, - textMessage.substring(Command.UNBAN_USER.length + userId.length) - .trim() - .takeIf { it.isNotBlank() } + trimParts(textMessage, messageParts.take(2)) ) } else { ParsedCommand.ErrorSyntax(Command.UNBAN_USER) @@ -254,7 +243,7 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.UNBAN_USER) } } - Command.IGNORE_USER.command -> { + Command.IGNORE_USER.matches(slashCommand) -> { if (messageParts.size == 2) { val userId = messageParts[1] @@ -267,7 +256,7 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.IGNORE_USER) } } - Command.UNIGNORE_USER.command -> { + Command.UNIGNORE_USER.matches(slashCommand) -> { if (messageParts.size == 2) { val userId = messageParts[1] @@ -280,7 +269,7 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.UNIGNORE_USER) } } - Command.SET_USER_POWER_LEVEL.command -> { + Command.SET_USER_POWER_LEVEL.matches(slashCommand) -> { if (messageParts.size == 3) { val userId = messageParts[1] if (MatrixPatterns.isUserId(userId)) { @@ -300,7 +289,7 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.SET_USER_POWER_LEVEL) } } - Command.RESET_USER_POWER_LEVEL.command -> { + Command.RESET_USER_POWER_LEVEL.matches(slashCommand) -> { if (messageParts.size == 2) { val userId = messageParts[1] @@ -313,7 +302,7 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.SET_USER_POWER_LEVEL) } } - Command.MARKDOWN.command -> { + Command.MARKDOWN.matches(slashCommand) -> { if (messageParts.size == 2) { when { "on".equals(messageParts[1], true) -> ParsedCommand.SetMarkdown(true) @@ -324,31 +313,34 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.MARKDOWN) } } - Command.CLEAR_SCALAR_TOKEN.command -> { + Command.CLEAR_SCALAR_TOKEN.matches(slashCommand) -> { if (messageParts.size == 1) { ParsedCommand.ClearScalarToken } else { ParsedCommand.ErrorSyntax(Command.CLEAR_SCALAR_TOKEN) } } - Command.SPOILER.command -> { - val message = textMessage.substring(Command.SPOILER.command.length).trim() - ParsedCommand.SendSpoiler(message) + Command.SPOILER.matches(slashCommand) -> { + if (message.isNotEmpty()) { + ParsedCommand.SendSpoiler(message) + } else { + ParsedCommand.ErrorSyntax(Command.SPOILER) + } } - Command.SHRUG.command -> { - val message = textMessage.substring(Command.SHRUG.command.length).trim() - + Command.SHRUG.matches(slashCommand) -> { ParsedCommand.SendShrug(message) } - Command.LENNY.command -> { - val message = textMessage.substring(Command.LENNY.command.length).trim() - + Command.LENNY.matches(slashCommand) -> { ParsedCommand.SendLenny(message) } - Command.DISCARD_SESSION.command -> { - ParsedCommand.DiscardSession + Command.DISCARD_SESSION.matches(slashCommand) -> { + if (messageParts.size == 1) { + ParsedCommand.DiscardSession + } else { + ParsedCommand.ErrorSyntax(Command.DISCARD_SESSION) + } } - Command.WHOIS.command -> { + Command.WHOIS.matches(slashCommand) -> { if (messageParts.size == 2) { val userId = messageParts[1] @@ -361,57 +353,49 @@ object CommandParser { ParsedCommand.ErrorSyntax(Command.WHOIS) } } - Command.CONFETTI.command -> { - val message = textMessage.substring(Command.CONFETTI.command.length).trim() + Command.CONFETTI.matches(slashCommand) -> { ParsedCommand.SendChatEffect(ChatEffect.CONFETTI, message) } - Command.SNOWFALL.command -> { - val message = textMessage.substring(Command.SNOWFALL.command.length).trim() + Command.SNOWFALL.matches(slashCommand) -> { ParsedCommand.SendChatEffect(ChatEffect.SNOWFALL, message) } - Command.CREATE_SPACE.command -> { - val rawCommand = textMessage.substring(Command.CREATE_SPACE.command.length).trim() - val split = rawCommand.split(" ").map { it.trim() } - if (split.isEmpty()) { - ParsedCommand.ErrorSyntax(Command.CREATE_SPACE) - } else { + Command.CREATE_SPACE.matches(slashCommand) -> { + if (messageParts.size >= 2) { ParsedCommand.CreateSpace( - split[0], - split.subList(1, split.size) + messageParts[1], + messageParts.drop(2) ) - } - } - Command.ADD_TO_SPACE.command -> { - val rawCommand = textMessage.substring(Command.ADD_TO_SPACE.command.length).trim() - ParsedCommand.AddToSpace( - rawCommand - ) - } - Command.JOIN_SPACE.command -> { - val spaceIdOrAlias = textMessage.substring(Command.JOIN_SPACE.command.length).trim() - ParsedCommand.JoinSpace( - spaceIdOrAlias - ) - } - Command.LEAVE_ROOM.command -> { - val spaceIdOrAlias = textMessage.substring(Command.LEAVE_ROOM.command.length).trim() - ParsedCommand.LeaveRoom( - spaceIdOrAlias - ) - } - Command.UPGRADE_ROOM.command -> { - val newVersion = textMessage.substring(Command.UPGRADE_ROOM.command.length).trim() - if (newVersion.isEmpty()) { - ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM) } else { - ParsedCommand.UpgradeRoom(newVersion) + ParsedCommand.ErrorSyntax(Command.CREATE_SPACE) } } - else -> { + Command.ADD_TO_SPACE.matches(slashCommand) -> { + ParsedCommand.AddToSpace(spaceId = message) + } + Command.JOIN_SPACE.matches(slashCommand) -> { + ParsedCommand.JoinSpace(spaceIdOrAlias = message) + } + Command.LEAVE_ROOM.matches(slashCommand) -> { + ParsedCommand.LeaveRoom(roomId = message) + } + Command.UPGRADE_ROOM.matches(slashCommand) -> { + if (message.isNotEmpty()) { + ParsedCommand.UpgradeRoom(newVersion = message) + } else { + ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM) + } + } + else -> { // Unknown command ParsedCommand.ErrorUnknownSlashCommand(slashCommand) } } } } + + private fun trimParts(message: CharSequence, messageParts: List): String? { + val partsSize = messageParts.sumOf { it.length } + val gapsNumber = messageParts.size - 1 + return message.substring(partsSize + gapsNumber).trim().takeIf { it.isNotEmpty() } + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 566cb2d2de..eaa684dea1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -2098,7 +2098,7 @@ class RoomDetailFragment @Inject constructor( userId == session.myUserId) { // Empty composer, current user: start an emote views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ") - views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.length) + views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.command.length + 1) } else { val roomMember = roomDetailViewModel.getMember(userId) // TODO move logic outside of fragment diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index d787978aa9..e907db0328 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -161,7 +161,7 @@ class MessageComposerViewModel @AssistedInject constructor( withState { state -> when (state.sendMode) { is SendMode.Regular -> { - when (val slashCommandResult = CommandParser.parseSplashCommand(action.text)) { + when (val slashCommandResult = CommandParser.parseSlashCommand(action.text)) { is ParsedCommand.ErrorNotACommand -> { // Send the text message to the room room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) From 9fa38c5cc5d27d00c1e9faf74356f214e50d4ee3 Mon Sep 17 00:00:00 2001 From: fedrunov Date: Tue, 11 Jan 2022 15:50:51 +0100 Subject: [PATCH 10/72] web version aliases --- .../src/main/java/im/vector/app/features/command/Command.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index 4edd62a488..385cf1f3db 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -34,12 +34,12 @@ enum class Command(val command: String, val aliases: Array?, val p RESET_USER_POWER_LEVEL("/deop", null, "", R.string.command_description_deop_user, false), ROOM_NAME("/roomname", null, "", R.string.command_description_room_name, false), INVITE("/invite", null, " [reason]", R.string.command_description_invite_user, false), - JOIN_ROOM("/join", null, " [reason]", R.string.command_description_join_room, false), + JOIN_ROOM("/join", arrayOf("/j", "/goto"), " [reason]", R.string.command_description_join_room, false), PART("/part", null, "[]", R.string.command_description_part_room, false), TOPIC("/topic", null, "", R.string.command_description_topic, false), REMOVE_USER("/remove", arrayOf("/kick"), " [reason]", R.string.command_description_kick_user, false), CHANGE_DISPLAY_NAME("/nick", null, "", R.string.command_description_nick, false), - CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", null, "", R.string.command_description_nick_for_room, false), + CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", arrayOf("/roomnick"), "", R.string.command_description_nick_for_room, false), ROOM_AVATAR("/roomavatar", null, "", R.string.command_description_room_avatar, true /* Since user has to know the mxc url */), CHANGE_AVATAR_FOR_ROOM("/myroomavatar", null, "", R.string.command_description_avatar_for_room, true /* Since user has to know the mxc url */), MARKDOWN("/markdown", null, "", R.string.command_description_markdown, false), From 672d4e591c7b01b7d1e03b32eabe7aaa2901ed7f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 11 Jan 2022 16:54:37 +0000 Subject: [PATCH 11/72] disabling the automatic carousel transitions on user interaction - extracts all the logic to its own extension --- .../FtueAuthSplashCarouselFragment.kt | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt index 152754f241..659508448a 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt @@ -80,17 +80,27 @@ class FtueAuthSplashCarouselFragment @Inject constructor( "Branch: ${BuildConfig.GIT_BRANCH_NAME}" views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) } } + views.splashCarousel.registerAutomaticUntilInteractionTransitions() + } - views.splashCarousel.apply { - var scheduledTransition: Job? = null - registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - scheduledTransition?.cancel() + private fun ViewPager2.registerAutomaticUntilInteractionTransitions() { + var scheduledTransition: Job? = null + registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + private var hasUserManuallyInteractedWithCarousel: Boolean = false + + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { + hasUserManuallyInteractedWithCarousel = !isFakeDragging + } + + override fun onPageSelected(position: Int) { + scheduledTransition?.cancel() + if (hasUserManuallyInteractedWithCarousel) { + // stop the automatic transitions + } else { scheduledTransition = scheduleCarouselTransition() } - }) - scheduledTransition = scheduleCarouselTransition() - } + } + }) } private fun ViewPager2.scheduleCarouselTransition(): Job { From ce7a93bcae5a1dbb43396cf453c7667d26c66518 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 11 Jan 2022 17:28:29 +0000 Subject: [PATCH 12/72] locking phones to portait during the ftue auth onboarding flow - uses a resource bucket flag for determining if the device is big enough to be considered a tablet and in turn, enable a landscape experience --- .../src/main/res/values-sw600dp/tablet.xml | 1 + .../ui-styles/src/main/res/values/tablet.xml | 1 + .../core/platform/ScreenOrientationLocker.kt | 42 +++++++++++++++++++ .../onboarding/OnboardingVariantFactory.kt | 5 ++- .../onboarding/ftueauth/FtueAuthVariant.kt | 6 ++- 5 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/platform/ScreenOrientationLocker.kt diff --git a/library/ui-styles/src/main/res/values-sw600dp/tablet.xml b/library/ui-styles/src/main/res/values-sw600dp/tablet.xml index 39f467cf0d..86bab06371 100644 --- a/library/ui-styles/src/main/res/values-sw600dp/tablet.xml +++ b/library/ui-styles/src/main/res/values-sw600dp/tablet.xml @@ -2,5 +2,6 @@ 0.6 + true \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/tablet.xml b/library/ui-styles/src/main/res/values/tablet.xml index a5df8fe17c..8460f0ccf8 100644 --- a/library/ui-styles/src/main/res/values/tablet.xml +++ b/library/ui-styles/src/main/res/values/tablet.xml @@ -2,5 +2,6 @@ 1 + false \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/core/platform/ScreenOrientationLocker.kt b/vector/src/main/java/im/vector/app/core/platform/ScreenOrientationLocker.kt new file mode 100644 index 0000000000..4b62090d3f --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/platform/ScreenOrientationLocker.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 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.core.platform + +import android.annotation.SuppressLint +import android.content.pm.ActivityInfo +import android.content.res.Resources +import androidx.appcompat.app.AppCompatActivity +import im.vector.app.R +import javax.inject.Inject + +class ScreenOrientationLocker @Inject constructor( + private val resources: Resources +) { + + // Some screens do not provide enough value for us to provide phone landscape experiences + @SuppressLint("SourceLockedOrientationActivity") + fun lockPhonesToPortrait(activity: AppCompatActivity) { + when (resources.getBoolean(R.bool.is_tablet)) { + true -> { + // do nothing + } + false -> { + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt index c171fc223d..52423d7019 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingVariantFactory.kt @@ -16,6 +16,7 @@ package im.vector.app.features.onboarding +import im.vector.app.core.platform.ScreenOrientationLocker import im.vector.app.databinding.ActivityLoginBinding import im.vector.app.features.VectorFeatures import im.vector.app.features.login2.LoginViewModel2 @@ -24,6 +25,7 @@ import javax.inject.Inject class OnboardingVariantFactory @Inject constructor( private val vectorFeatures: VectorFeatures, + private val orientationLocker: ScreenOrientationLocker, ) { fun create(activity: OnboardingActivity, @@ -37,7 +39,8 @@ class OnboardingVariantFactory @Inject constructor( onboardingViewModel = onboardingViewModel.value, activity = activity, supportFragmentManager = activity.supportFragmentManager, - vectorFeatures = vectorFeatures + vectorFeatures = vectorFeatures, + orientationLocker = orientationLocker ) VectorFeatures.OnboardingVariant.LOGIN_2 -> Login2Variant( views = views, diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index f177eda114..06dbad50f8 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -15,6 +15,7 @@ */ package im.vector.app.features.onboarding.ftueauth + import android.content.Intent import android.view.View import android.view.ViewGroup @@ -31,6 +32,7 @@ import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.platform.ScreenOrientationLocker import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityLoginBinding import im.vector.app.features.VectorFeatures @@ -62,7 +64,8 @@ class FtueAuthVariant( private val onboardingViewModel: OnboardingViewModel, private val activity: VectorBaseActivity, private val supportFragmentManager: FragmentManager, - private val vectorFeatures: VectorFeatures + private val vectorFeatures: VectorFeatures, + private val orientationLocker: ScreenOrientationLocker, ) : OnboardingVariant { private val enterAnim = R.anim.enter_fade_in @@ -91,6 +94,7 @@ class FtueAuthVariant( } with(activity) { + orientationLocker.lockPhonesToPortrait(this) onboardingViewModel.onEach { updateWithState(it) } From 01ef46517e0358d0446a9b13fd0612e6286c4883 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 12 Jan 2022 09:16:46 +0000 Subject: [PATCH 13/72] flattening branch to avoid empty code block --- .../onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt index 659508448a..6c1c57e0fc 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt @@ -94,9 +94,8 @@ class FtueAuthSplashCarouselFragment @Inject constructor( override fun onPageSelected(position: Int) { scheduledTransition?.cancel() - if (hasUserManuallyInteractedWithCarousel) { - // stop the automatic transitions - } else { + // only schedule automatic transitions whilst the user has not interacted with the carousel + if (!hasUserManuallyInteractedWithCarousel) { scheduledTransition = scheduleCarouselTransition() } } From 7cea0352f921b4721f4a2c3dd93ee464da3fa0f6 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 13 Dec 2021 14:21:00 +0000 Subject: [PATCH 14/72] adding debug feature flag for the splash carousel --- .../app/features/debug/features/DebugFeaturesStateFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index 6ddbb53134..14d0fdc254 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -40,7 +40,7 @@ class DebugFeaturesStateFactory @Inject constructor( key = DebugFeatureKeys.alreadyHaveAnAccount ), createBooleanFeature( - label = "FTUE Splash - Carousel", + label = "FTUE Splash - carousel", factory = VectorFeatures::isSplashCarouselEnabled, key = DebugFeatureKeys.splashCarousel ) From 1ae112daaefce0c147428dd47cd41e004eb1218f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 6 Jan 2022 12:10:30 +0000 Subject: [PATCH 15/72] adding feature flag and entry point for the _wip_ usecase screen --- .../debug/features/DebugFeaturesStateFactory.kt | 13 +++++++++---- .../debug/features/DebugVectorFeatures.kt | 13 ++++++++----- .../im/vector/app/features/VectorFeatures.kt | 12 ++++++------ .../features/onboarding/OnboardingViewEvents.kt | 1 + .../features/onboarding/OnboardingViewModel.kt | 17 ++++++++++++++--- .../ftueauth/FtueAuthSplashCarouselFragment.kt | 4 ++-- .../ftueauth/FtueAuthSplashFragment.kt | 4 ++-- .../onboarding/ftueauth/FtueAuthVariant.kt | 6 +++++- 8 files changed, 47 insertions(+), 23 deletions(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index 14d0fdc254..fb803162a7 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -36,13 +36,18 @@ class DebugFeaturesStateFactory @Inject constructor( ), createBooleanFeature( label = "FTUE Splash - I already have an account", - factory = VectorFeatures::isAlreadyHaveAccountSplashEnabled, - key = DebugFeatureKeys.alreadyHaveAnAccount + key = DebugFeatureKeys.onboardingAlreadyHaveAnAccount, + factory = VectorFeatures::isOnboardingAlreadyHaveAccountSplashEnabled ), createBooleanFeature( label = "FTUE Splash - carousel", - factory = VectorFeatures::isSplashCarouselEnabled, - key = DebugFeatureKeys.splashCarousel + key = DebugFeatureKeys.onboardingSplashCarousel, + factory = VectorFeatures::isOnboardingSplashCarouselEnabled + ), + createBooleanFeature( + label = "FTUE Use Case", + key = DebugFeatureKeys.onboardingUseCase, + factory = VectorFeatures::isOnboardingUseCaseEnabled ) )) } diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 2e11017ef3..e0598067db 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -43,10 +43,12 @@ class DebugVectorFeatures( return readPreferences().getEnum() ?: vectorFeatures.onboardingVariant() } - override fun isAlreadyHaveAccountSplashEnabled(): Boolean = read(DebugFeatureKeys.alreadyHaveAnAccount) - ?: vectorFeatures.isAlreadyHaveAccountSplashEnabled() + override fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean = read(DebugFeatureKeys.onboardingAlreadyHaveAnAccount) + ?: vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled() - override fun isSplashCarouselEnabled(): Boolean = read(DebugFeatureKeys.splashCarousel) ?: vectorFeatures.isSplashCarouselEnabled() + override fun isOnboardingSplashCarouselEnabled(): Boolean = read(DebugFeatureKeys.onboardingSplashCarousel) ?: vectorFeatures.isOnboardingSplashCarouselEnabled() + + override fun isOnboardingUseCaseEnabled(): Boolean = read(DebugFeatureKeys.onboardingUseCase) ?: vectorFeatures.isOnboardingUseCaseEnabled() fun override(value: T?, key: Preferences.Key) = updatePreferences { if (value == null) { @@ -96,6 +98,7 @@ private inline fun > enumPreferencesKey() = enumPreferencesK private fun > enumPreferencesKey(type: KClass) = stringPreferencesKey("enum-${type.simpleName}") object DebugFeatureKeys { - val alreadyHaveAnAccount = booleanPreferencesKey("already-have-an-account") - val splashCarousel = booleanPreferencesKey("splash-carousel") + val onboardingAlreadyHaveAnAccount = booleanPreferencesKey("onboarding-already-have-an-account") + val onboardingSplashCarousel = booleanPreferencesKey("onboarding-splash-carousel") + val onboardingUseCase = booleanPreferencesKey("onbboarding-splash-carousel") } diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index c53ff0f433..25c204f2ef 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -21,10 +21,9 @@ import im.vector.app.BuildConfig interface VectorFeatures { fun onboardingVariant(): OnboardingVariant - - fun isAlreadyHaveAccountSplashEnabled(): Boolean - - fun isSplashCarouselEnabled(): Boolean + fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean + fun isOnboardingSplashCarouselEnabled(): Boolean + fun isOnboardingUseCaseEnabled(): Boolean enum class OnboardingVariant { LEGACY, @@ -35,6 +34,7 @@ interface VectorFeatures { class DefaultVectorFeatures : VectorFeatures { override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT - override fun isAlreadyHaveAccountSplashEnabled() = true - override fun isSplashCarouselEnabled() = false + override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true + override fun isOnboardingSplashCarouselEnabled() = false + override fun isOnboardingUseCaseEnabled() = false } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt index ab782a9908..d6105cda13 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt @@ -34,6 +34,7 @@ sealed class OnboardingViewEvents : VectorViewEvents { // Navigation event + object OpenUseCaseSelection : OnboardingViewEvents() object OpenServerSelection : OnboardingViewEvents() data class OnServerSelectionDone(val serverType: ServerType) : OnboardingViewEvents() object OnLoginFlowRetrieved : OnboardingViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 5d1e0fdade..39c2e9bcdb 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -35,6 +35,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.ensureTrailingSlash +import im.vector.app.features.VectorFeatures import im.vector.app.features.login.HomeServerConnectionConfigFactory import im.vector.app.features.login.LoginConfig import im.vector.app.features.login.LoginMode @@ -71,7 +72,8 @@ class OnboardingViewModel @AssistedInject constructor( private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory, private val reAuthHelper: ReAuthHelper, private val stringProvider: StringProvider, - private val homeServerHistoryService: HomeServerHistoryService + private val homeServerHistoryService: HomeServerHistoryService, + private val vectorFeatures: VectorFeatures ) : VectorViewModel(initialState) { @AssistedFactory @@ -154,15 +156,24 @@ class OnboardingViewModel @AssistedInject constructor( if (homeServerConnectionConfig == null) { // Url is invalid, in this case, just use the regular flow Timber.w("Url from config url was invalid: $configUrl") - _viewEvents.post(OnboardingViewEvents.OpenServerSelection) + continueToPageAfterSplash() } else { getLoginFlow(homeServerConnectionConfig, ServerType.Other) } } else { - _viewEvents.post(OnboardingViewEvents.OpenServerSelection) + continueToPageAfterSplash() } } + private fun continueToPageAfterSplash() { + val nextOnboardingStep = if (vectorFeatures.isOnboardingUseCaseEnabled()) { + OnboardingViewEvents.OpenUseCaseSelection + } else { + OnboardingViewEvents.OpenServerSelection + } + _viewEvents.post(nextOnboardingStep) + } + private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) { // It happens when we get the login flow, or during direct authentication. // So alter the homeserver config and retrieve again the login flow diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt index 152754f241..f946e4828c 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt @@ -69,7 +69,7 @@ class FtueAuthSplashCarouselFragment @Inject constructor( views.loginSplashSubmit.debouncedClicks { getStarted() } views.loginSplashAlreadyHaveAccount.apply { - isVisible = vectorFeatures.isAlreadyHaveAccountSplashEnabled() + isVisible = vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled() debouncedClicks { alreadyHaveAnAccount() } } @@ -102,7 +102,7 @@ class FtueAuthSplashCarouselFragment @Inject constructor( } private fun getStarted() { - val getStartedFlow = if (vectorFeatures.isAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp + val getStartedFlow = if (vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow)) } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt index f8f1d7919b..fd63889fd6 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashFragment.kt @@ -55,7 +55,7 @@ class FtueAuthSplashFragment @Inject constructor( private fun setupViews() { views.loginSplashSubmit.debouncedClicks { getStarted() } views.loginSplashAlreadyHaveAccount.apply { - isVisible = vectorFeatures.isAlreadyHaveAccountSplashEnabled() + isVisible = vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled() debouncedClicks { alreadyHaveAnAccount() } } @@ -70,7 +70,7 @@ class FtueAuthSplashFragment @Inject constructor( } private fun getStarted() { - val getStartedFlow = if (vectorFeatures.isAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp + val getStartedFlow = if (vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow)) } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index f177eda114..08258062e7 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -15,6 +15,7 @@ */ package im.vector.app.features.onboarding.ftueauth + import android.content.Intent import android.view.View import android.view.ViewGroup @@ -109,7 +110,7 @@ class FtueAuthVariant( } private fun addFirstFragment() { - val splashFragment = when (vectorFeatures.isSplashCarouselEnabled()) { + val splashFragment = when (vectorFeatures.isOnboardingSplashCarouselEnabled()) { true -> FtueAuthSplashCarouselFragment::class.java else -> FtueAuthSplashFragment::class.java } @@ -208,6 +209,9 @@ class FtueAuthVariant( is OnboardingViewEvents.Loading -> // This is handled by the Fragments Unit + OnboardingViewEvents.OpenUseCaseSelection -> { + TODO() + } }.exhaustive } From b6ff6aa4cc43dcf1f6b49b91335fde9e8a7c9433 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 7 Jan 2022 12:56:06 +0000 Subject: [PATCH 16/72] adding barebones ftue use case fragment --- .../im/vector/app/core/di/FragmentModule.kt | 6 + .../features/onboarding/OnboardingAction.kt | 1 + .../onboarding/OnboardingViewModel.kt | 7 + .../ftueauth/FtueAuthUseCaseFragment.kt | 54 +++++++ .../onboarding/ftueauth/FtueAuthVariant.kt | 4 +- .../src/main/res/drawable/ic_communities.xml | 18 +++ .../res/drawable/ic_friends_and_family.xml | 16 ++ .../drawable/ic_onboarding_use_case_icon.xml | 14 ++ vector/src/main/res/drawable/ic_teams.xml | 26 ++++ .../layout/fragment_ftue_auth_use_case.xml | 144 ++++++++++++++++++ 10 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt create mode 100644 vector/src/main/res/drawable/ic_communities.xml create mode 100644 vector/src/main/res/drawable/ic_friends_and_family.xml create mode 100644 vector/src/main/res/drawable/ic_onboarding_use_case_icon.xml create mode 100644 vector/src/main/res/drawable/ic_teams.xml create mode 100644 vector/src/main/res/layout/fragment_ftue_auth_use_case.xml diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index c27309fad6..626cf90592 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -104,6 +104,7 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthServerSelectionFragmen import im.vector.app.features.onboarding.ftueauth.FtueAuthSignUpSignInSelectionFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashCarouselFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashFragment +import im.vector.app.features.onboarding.ftueauth.FtueAuthUseCaseFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthWaitForEmailFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthWebFragment import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment @@ -449,6 +450,11 @@ interface FragmentModule { @FragmentKey(FtueAuthSplashCarouselFragment::class) fun bindFtueAuthSplashCarouselFragment(fragment: FtueAuthSplashCarouselFragment): Fragment + @Binds + @IntoMap + @FragmentKey(FtueAuthUseCaseFragment::class) + fun bindFtueAuthUseCaseFragment(fragment: FtueAuthUseCaseFragment): Fragment + @Binds @IntoMap @FragmentKey(FtueAuthWaitForEmailFragment::class) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index bb1d3cc52d..ea549b214a 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -31,6 +31,7 @@ sealed class OnboardingAction : VectorViewModelAction { data class UpdateServerType(val serverType: ServerType) : OnboardingAction() data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction() + data class UpdateUseCase(val todo: String) : OnboardingAction() data class UpdateSignMode(val signMode: SignMode) : OnboardingAction() data class LoginWithToken(val loginToken: String) : OnboardingAction() data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 39c2e9bcdb..5a098ebb56 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -125,6 +125,7 @@ class OnboardingViewModel @AssistedInject constructor( when (action) { is OnboardingAction.OnGetStarted -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow) is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow) + is OnboardingAction.UpdateUseCase -> handleUpdateUseCase(action) is OnboardingAction.UpdateServerType -> handleUpdateServerType(action) is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action) is OnboardingAction.InitWith -> handleInitWith(action) @@ -452,6 +453,12 @@ class OnboardingViewModel @AssistedInject constructor( } } + private fun handleUpdateUseCase(action: OnboardingAction.UpdateUseCase) { + // TODO act on the use case selection + action.todo + _viewEvents.post(OnboardingViewEvents.OpenServerSelection) + } + private fun handleUpdateServerType(action: OnboardingAction.UpdateServerType) { setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt new file mode 100644 index 0000000000..13304bf124 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt @@ -0,0 +1,54 @@ +/* + * 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.onboarding.ftueauth + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import im.vector.app.databinding.FragmentFtueAuthUseCaseBinding +import im.vector.app.features.login.ServerType +import im.vector.app.features.onboarding.OnboardingAction +import javax.inject.Inject + +class FtueAuthUseCaseFragment @Inject constructor() : AbstractFtueAuthFragment() { + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAuthUseCaseBinding { + return FragmentFtueAuthUseCaseBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupViews() + } + + private fun setupViews() { + views.useCaseOptionOne.debouncedClicks { + viewModel.handle(OnboardingAction.UpdateUseCase("todo")) + } + views.useCaseOptionTwo.debouncedClicks { + viewModel.handle(OnboardingAction.UpdateUseCase("todo")) + } + views.useCaseOptionThree.debouncedClicks { + viewModel.handle(OnboardingAction.UpdateUseCase("todo")) + } + } + + override fun resetViewModel() { + // Nothing to do + } +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 08258062e7..4ef99e355e 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -210,7 +210,9 @@ class FtueAuthVariant( // This is handled by the Fragments Unit OnboardingViewEvents.OpenUseCaseSelection -> { - TODO() + activity.addFragmentToBackstack(views.loginFragmentContainer, + FtueAuthUseCaseFragment::class.java, + option = commonOption) } }.exhaustive } diff --git a/vector/src/main/res/drawable/ic_communities.xml b/vector/src/main/res/drawable/ic_communities.xml new file mode 100644 index 0000000000..f550de8106 --- /dev/null +++ b/vector/src/main/res/drawable/ic_communities.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/vector/src/main/res/drawable/ic_friends_and_family.xml b/vector/src/main/res/drawable/ic_friends_and_family.xml new file mode 100644 index 0000000000..d7ac86f240 --- /dev/null +++ b/vector/src/main/res/drawable/ic_friends_and_family.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/vector/src/main/res/drawable/ic_onboarding_use_case_icon.xml b/vector/src/main/res/drawable/ic_onboarding_use_case_icon.xml new file mode 100644 index 0000000000..35b45aa69a --- /dev/null +++ b/vector/src/main/res/drawable/ic_onboarding_use_case_icon.xml @@ -0,0 +1,14 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_teams.xml b/vector/src/main/res/drawable/ic_teams.xml new file mode 100644 index 0000000000..8745cfd2d4 --- /dev/null +++ b/vector/src/main/res/drawable/ic_teams.xml @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml b/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml new file mode 100644 index 0000000000..fbc4db43be --- /dev/null +++ b/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 00bbede8025e3a5d399727628ba1709aae8bb58c Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 10 Jan 2022 11:57:05 +0000 Subject: [PATCH 17/72] respecting the underline parameter by only applying an underline when it's set - updates the default value to true and that was the existing intentional behaviour --- vector/src/main/java/im/vector/app/core/extensions/TextView.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/TextView.kt b/vector/src/main/java/im/vector/app/core/extensions/TextView.kt index adb655f169..048dddf9e5 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TextView.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TextView.kt @@ -82,7 +82,7 @@ fun TextView.setTextWithColoredPart(@StringRes fullTextRes: Int, fun TextView.setTextWithColoredPart(fullText: String, coloredPart: String, @AttrRes colorAttribute: Int = R.attr.colorPrimary, - underline: Boolean = false, + underline: Boolean = true, onClick: (() -> Unit)? = null) { val color = ThemeUtils.getColor(context, colorAttribute) @@ -101,7 +101,6 @@ fun TextView.setTextWithColoredPart(fullText: String, override fun updateDrawState(ds: TextPaint) { ds.color = color - ds.isUnderlineText = !underline } } setSpan(clickableSpan, index, index + coloredPart.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) From 3d4caeaa75c6eabc49bb7ab7065bcf1341ea0d61 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 10 Jan 2022 11:57:26 +0000 Subject: [PATCH 18/72] extracting use case copy to the resources --- .../ftueauth/FtueAuthUseCaseFragment.kt | 16 +++++++ .../layout/fragment_ftue_auth_use_case.xml | 43 ++++++++++++++----- vector/src/main/res/values/strings.xml | 10 +++++ 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt index 13304bf124..67476077bd 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt @@ -20,9 +20,12 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import im.vector.app.R +import im.vector.app.core.extensions.setTextWithColoredPart import im.vector.app.databinding.FragmentFtueAuthUseCaseBinding import im.vector.app.features.login.ServerType import im.vector.app.features.onboarding.OnboardingAction +import me.saket.bettermovementmethod.BetterLinkMovementMethod import javax.inject.Inject class FtueAuthUseCaseFragment @Inject constructor() : AbstractFtueAuthFragment() { @@ -46,6 +49,19 @@ class FtueAuthUseCaseFragment @Inject constructor() : AbstractFtueAuthFragment + + + app:layout_constraintTop_toBottomOf="@id/titleContentSpacing" /> Cut the slack from teams. As universal as email, Element is a completely new type of collaboration. + Who will you chat to the most? + We\'ll help you get connected. + Friends and family + Teams + Communities + Not sure yet? %s + You can skip this question + Looking to join an existing server? + Connect to server + It\'s your conversation. Own it. Chat with people directly or in groups Keep conversations private with encryption From 8c67cc007661c8dcdf5911a406350aa93fc5d330 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 10 Jan 2022 12:07:55 +0000 Subject: [PATCH 19/72] only showing the use case screen for the sign up flow --- .../features/onboarding/OnboardingViewModel.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 5a098ebb56..a61db35832 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -157,20 +157,24 @@ class OnboardingViewModel @AssistedInject constructor( if (homeServerConnectionConfig == null) { // Url is invalid, in this case, just use the regular flow Timber.w("Url from config url was invalid: $configUrl") - continueToPageAfterSplash() + continueToPageAfterSplash(onboardingFlow) } else { getLoginFlow(homeServerConnectionConfig, ServerType.Other) } } else { - continueToPageAfterSplash() + continueToPageAfterSplash(onboardingFlow) } } - private fun continueToPageAfterSplash() { - val nextOnboardingStep = if (vectorFeatures.isOnboardingUseCaseEnabled()) { - OnboardingViewEvents.OpenUseCaseSelection - } else { - OnboardingViewEvents.OpenServerSelection + private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) { + val nextOnboardingStep = when (onboardingFlow) { + OnboardingFlow.SignUp -> if (vectorFeatures.isOnboardingUseCaseEnabled()) { + OnboardingViewEvents.OpenUseCaseSelection + } else { + OnboardingViewEvents.OpenServerSelection + } + OnboardingFlow.SignIn, + OnboardingFlow.SignInSignUp -> OnboardingViewEvents.OpenServerSelection } _viewEvents.post(nextOnboardingStep) } From 0ba6f55ad4cb71291036c2c2ba2eb2d66734cf0a Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 10 Jan 2022 13:06:44 +0000 Subject: [PATCH 20/72] aligning the use case image padding to the other onboarding pages --- vector/src/main/res/layout/fragment_ftue_auth_use_case.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml b/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml index ab1b7f1718..1f2968a428 100644 --- a/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml +++ b/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml @@ -25,7 +25,7 @@ android:id="@+id/useCaseHeaderIcon" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="36dp" + android:layout_marginTop="52dp" android:contentDescription="@null" android:src="@drawable/ic_onboarding_use_case_icon" app:layout_constraintBottom_toTopOf="@id/useCaseHeaderTitle" From 878371cd9a6d50171b8e5ec7b7cee8cb058b6df8 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 10 Jan 2022 13:07:12 +0000 Subject: [PATCH 21/72] fading the server selection when coming from the use case page --- .../onboarding/ftueauth/FtueAuthVariant.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt index 4ef99e355e..eab6591984 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt @@ -152,13 +152,16 @@ class FtueAuthVariant( activity.addFragmentToBackstack(views.loginFragmentContainer, FtueAuthServerSelectionFragment::class.java, option = { ft -> - activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // Disable transition of text - // findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // No transition here now actually - // findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } - // TODO Disabled because it provokes a flickering - // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) + if (vectorFeatures.isOnboardingUseCaseEnabled()) { + ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) + } else { + activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + // TODO Disabled because it provokes a flickering + // Disable transition of text + // findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + // No transition here now actually + // findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") } + } }) is OnboardingViewEvents.OnServerSelectionDone -> onServerSelectionDone(viewEvents) is OnboardingViewEvents.OnSignModeSelected -> onSignModeSelected(viewEvents) From c29dc8975693cdd342f924f441510f1f15de1b90 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 10 Jan 2022 13:07:46 +0000 Subject: [PATCH 22/72] typing the use case selections and binding the label and usecase type at the same time for visibility --- .../features/onboarding/OnboardingAction.kt | 10 +++++++- .../onboarding/OnboardingViewModel.kt | 2 +- .../ftueauth/FtueAuthUseCaseFragment.kt | 25 +++++++++++-------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index ea549b214a..9ce84211f1 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -31,7 +31,15 @@ sealed class OnboardingAction : VectorViewModelAction { data class UpdateServerType(val serverType: ServerType) : OnboardingAction() data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction() - data class UpdateUseCase(val todo: String) : OnboardingAction() + data class UpdateUseCase(val useCase: UseCase) : OnboardingAction() { + enum class UseCase { + FRIENDS_FAMILY, + TEAMS, + COMMUNITIES, + SKIP + } + } + data class UpdateSignMode(val signMode: SignMode) : OnboardingAction() data class LoginWithToken(val loginToken: String) : OnboardingAction() data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index a61db35832..0fa0a34dc6 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -459,7 +459,7 @@ class OnboardingViewModel @AssistedInject constructor( private fun handleUpdateUseCase(action: OnboardingAction.UpdateUseCase) { // TODO act on the use case selection - action.todo + action.useCase _viewEvents.post(OnboardingViewEvents.OpenServerSelection) } diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt index 67476077bd..39e8c3d8e0 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt @@ -20,12 +20,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.TextView +import androidx.annotation.StringRes import im.vector.app.R import im.vector.app.core.extensions.setTextWithColoredPart import im.vector.app.databinding.FragmentFtueAuthUseCaseBinding import im.vector.app.features.login.ServerType import im.vector.app.features.onboarding.OnboardingAction -import me.saket.bettermovementmethod.BetterLinkMovementMethod +import im.vector.app.features.onboarding.OnboardingAction.UpdateUseCase.UseCase import javax.inject.Inject class FtueAuthUseCaseFragment @Inject constructor() : AbstractFtueAuthFragment() { @@ -40,15 +42,9 @@ class FtueAuthUseCaseFragment @Inject constructor() : AbstractFtueAuthFragment Date: Mon, 10 Jan 2022 14:35:42 +0000 Subject: [PATCH 23/72] removing unused action instance (will be needed once we decide how to act on the use caseselection) --- .../im/vector/app/features/onboarding/OnboardingViewModel.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 0fa0a34dc6..5e0c294b3e 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -125,7 +125,7 @@ class OnboardingViewModel @AssistedInject constructor( when (action) { is OnboardingAction.OnGetStarted -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow) is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow) - is OnboardingAction.UpdateUseCase -> handleUpdateUseCase(action) + is OnboardingAction.UpdateUseCase -> handleUpdateUseCase() is OnboardingAction.UpdateServerType -> handleUpdateServerType(action) is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action) is OnboardingAction.InitWith -> handleInitWith(action) @@ -457,9 +457,8 @@ class OnboardingViewModel @AssistedInject constructor( } } - private fun handleUpdateUseCase(action: OnboardingAction.UpdateUseCase) { + private fun handleUpdateUseCase() { // TODO act on the use case selection - action.useCase _viewEvents.post(OnboardingViewEvents.OpenServerSelection) } From b92cb753f94347dca498ef5fe1c2c80a69816234 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 12 Jan 2022 15:31:54 +0200 Subject: [PATCH 24/72] Fix stuck local echo events at the bottom of the screen --- changelog.d/516.bugfix | 1 + .../sync/handler/room/RoomSyncHandler.kt | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 changelog.d/516.bugfix diff --git a/changelog.d/516.bugfix b/changelog.d/516.bugfix new file mode 100644 index 0000000000..7a5f745c32 --- /dev/null +++ b/changelog.d/516.bugfix @@ -0,0 +1 @@ +Fix for stuck local event messages at the bottom of the screen diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 24722445be..4a33363e82 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -129,7 +129,10 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } else { handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountJoinedRooms, 0.6f) { handleJoinedRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis, aggregator) + }.also { + fixStuckLocalEcho(it) } + } } is HandlingStrategy.INVITED -> @@ -478,4 +481,47 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle return result } + + /** + * There are multiple issues like #516 that report stuck local echo events + * at the bottom of each room timeline. + * + * That can happen when a message is SENT but not received back from the /sync. + * Until now we use unsignedData.transactionId to determine whether or not the local + * event should be deleted on every /sync. However, this is partially correct, lets have a look + * at the following scenario: + * + * [There is no Internet connection] --> [10 Messages are sent] --> [The 10 messages are in the queue] --> + * [Internet comes back for 1 second] --> [3 messages are sent] --> [Internet drops again] --> + * [No /sync response is triggered | home server can even replied with /sync but never arrived while we are offline] + * + * So the state until now is that we have 7 pending events to send and 3 sent but not received them back from /sync + * Subsequently, those 3 local messages will not be deleted while there is no transactionId from the /sync + * + * lets continue: + * [Now lets assume that in the same room another user sent 15 events] --> + * [We are finally back online!] --> + * [We will receive the 10 latest events for the room and of course sent the pending 7 messages] --> + * Now /sync response will NOT contain the 3 local messages so our events will stuck in the device. + * + * Someone can say, yes but it will come with the rooms/{roomId}/messages while paginating, + * so the problem will be solved. No that is not the case for two reasons: + * 1. rooms/{roomId}/messages response do not contain the unsignedData.transactionId so we cannot know which event + * to delete + * 2. even if transactionId was there, currently we are not deleting it from the pagination + * + * --------------------------------------------------------------------------------------------- + * While we cannot know when a specific event arrived from the pagination (no transactionId included), after each room /sync + * we clear all SENT events, and we are sure that we will receive it from /sync or pagination + */ + private fun fixStuckLocalEcho(rooms: List){ + rooms.forEach { roomEntity -> + roomEntity.sendingTimelineEvents.filter { timelineEvent -> + timelineEvent.root?.sendState == SendState.SENT + }.forEach { + roomEntity.sendingTimelineEvents.remove(it) + it.deleteOnCascade(true) + } + } + } } From 42e6c54e8f5641580889db58b0292ce6bf205906 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 12 Jan 2022 13:51:53 +0000 Subject: [PATCH 25/72] fixing line length --- .../vector/app/features/debug/features/DebugVectorFeatures.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index e0598067db..6ca33ca968 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -46,7 +46,8 @@ class DebugVectorFeatures( override fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean = read(DebugFeatureKeys.onboardingAlreadyHaveAnAccount) ?: vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled() - override fun isOnboardingSplashCarouselEnabled(): Boolean = read(DebugFeatureKeys.onboardingSplashCarousel) ?: vectorFeatures.isOnboardingSplashCarouselEnabled() + override fun isOnboardingSplashCarouselEnabled(): Boolean = read(DebugFeatureKeys.onboardingSplashCarousel) + ?: vectorFeatures.isOnboardingSplashCarouselEnabled() override fun isOnboardingUseCaseEnabled(): Boolean = read(DebugFeatureKeys.onboardingUseCase) ?: vectorFeatures.isOnboardingUseCaseEnabled() From 6f5f773360addb43dc3ee9b20c6853e60f53e600 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 12 Jan 2022 16:45:15 +0200 Subject: [PATCH 26/72] Fix kltint errors --- .../sdk/internal/session/sync/handler/room/RoomSyncHandler.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 4a33363e82..7d34d41e52 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -132,7 +132,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle }.also { fixStuckLocalEcho(it) } - } } is HandlingStrategy.INVITED -> @@ -514,7 +513,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle * While we cannot know when a specific event arrived from the pagination (no transactionId included), after each room /sync * we clear all SENT events, and we are sure that we will receive it from /sync or pagination */ - private fun fixStuckLocalEcho(rooms: List){ + private fun fixStuckLocalEcho(rooms: List) { rooms.forEach { roomEntity -> roomEntity.sendingTimelineEvents.filter { timelineEvent -> timelineEvent.root?.sendState == SendState.SENT From 18359fedb37b1984786db469e3dea2d1b60cabcb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 12 Jan 2022 18:06:32 +0100 Subject: [PATCH 27/72] Fix #4919 --- changelog.d/4935.bugfix | 1 + .../android/sdk/internal/session/terms/DefaultTermsService.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/4935.bugfix diff --git a/changelog.d/4935.bugfix b/changelog.d/4935.bugfix new file mode 100644 index 0000000000..18967ed4a5 --- /dev/null +++ b/changelog.d/4935.bugfix @@ -0,0 +1 @@ +Fix a wrong network error issue in the Legals screen \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt index 313fb6319d..6205e3e4b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt @@ -64,7 +64,7 @@ internal class DefaultTermsService @Inject constructor( */ override suspend fun getHomeserverTerms(baseUrl: String): TermsResponse { return try { - val request = baseUrl + NetworkConstants.URI_API_PREFIX_PATH_R0 + "register" + val request = baseUrl.ensureTrailingSlash() + NetworkConstants.URI_API_PREFIX_PATH_R0 + "register" executeRequest(null) { termsAPI.register(request) } From 6f8533c7d6f35b3b26bbf45c7b91283700f31ba1 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 13 Jan 2022 09:46:18 +0000 Subject: [PATCH 28/72] locking the analytics opt in screen to portrait for phones --- .../features/analytics/ui/consent/AnalyticsOptInActivity.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt index f6a06ebdb7..c84031d2fd 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt @@ -20,8 +20,10 @@ import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.platform.ScreenOrientationLocker import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding +import javax.inject.Inject /** * Simple container for AnalyticsOptInFragment @@ -29,6 +31,8 @@ import im.vector.app.databinding.ActivitySimpleBinding @AndroidEntryPoint class AnalyticsOptInActivity : VectorBaseActivity() { + @Inject lateinit var orientationLocker: ScreenOrientationLocker + private val viewModel: AnalyticsConsentViewModel by viewModel() override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) @@ -36,6 +40,7 @@ class AnalyticsOptInActivity : VectorBaseActivity() { override fun getCoordinatorLayout() = views.coordinatorLayout override fun initUiAndData() { + orientationLocker.lockPhonesToPortrait(this) if (isFirstCreation()) { addFragment(views.simpleFragmentContainer, AnalyticsOptInFragment::class.java) } From 5d0c55b6179cc8335ee3ab2cc16322ff7a687c12 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 13 Jan 2022 10:05:07 +0000 Subject: [PATCH 30/72] extracting usecase enum to its own file --- .../app/features/onboarding/FtueUseCase.kt | 24 +++++++++++++++++++ .../features/onboarding/OnboardingAction.kt | 10 +------- .../ftueauth/FtueAuthUseCaseFragment.kt | 12 +++++----- 3 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/onboarding/FtueUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/onboarding/FtueUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/FtueUseCase.kt new file mode 100644 index 0000000000..e720b7307c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/onboarding/FtueUseCase.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 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.onboarding + +enum class FtueUseCase { + FRIENDS_FAMILY, + TEAMS, + COMMUNITIES, + SKIP +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index 9ce84211f1..cfacd7a8d9 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -31,15 +31,7 @@ sealed class OnboardingAction : VectorViewModelAction { data class UpdateServerType(val serverType: ServerType) : OnboardingAction() data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction() - data class UpdateUseCase(val useCase: UseCase) : OnboardingAction() { - enum class UseCase { - FRIENDS_FAMILY, - TEAMS, - COMMUNITIES, - SKIP - } - } - + data class UpdateUseCase(val useCase: FtueUseCase) : OnboardingAction() data class UpdateSignMode(val signMode: SignMode) : OnboardingAction() data class LoginWithToken(val loginToken: String) : OnboardingAction() data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt index 39e8c3d8e0..da909c9352 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt @@ -27,7 +27,7 @@ import im.vector.app.core.extensions.setTextWithColoredPart import im.vector.app.databinding.FragmentFtueAuthUseCaseBinding import im.vector.app.features.login.ServerType import im.vector.app.features.onboarding.OnboardingAction -import im.vector.app.features.onboarding.OnboardingAction.UpdateUseCase.UseCase +import im.vector.app.features.onboarding.FtueUseCase import javax.inject.Inject class FtueAuthUseCaseFragment @Inject constructor() : AbstractFtueAuthFragment() { @@ -42,9 +42,9 @@ class FtueAuthUseCaseFragment @Inject constructor() : AbstractFtueAuthFragment Date: Thu, 13 Jan 2022 10:22:46 +0000 Subject: [PATCH 31/72] reusing existing helper to replace partial in string template --- .../features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt index da909c9352..5df10b7f5e 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt @@ -46,10 +46,9 @@ class FtueAuthUseCaseFragment @Inject constructor() : AbstractFtueAuthFragment Date: Thu, 13 Jan 2022 10:30:40 +0000 Subject: [PATCH 32/72] adding reset use case action, is todo until the persistence is implemented --- .../im/vector/app/features/onboarding/OnboardingAction.kt | 1 + .../im/vector/app/features/onboarding/OnboardingViewModel.kt | 5 +++++ .../features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt index cfacd7a8d9..2ca6a1f2fd 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt @@ -32,6 +32,7 @@ sealed class OnboardingAction : VectorViewModelAction { data class UpdateServerType(val serverType: ServerType) : OnboardingAction() data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction() data class UpdateUseCase(val useCase: FtueUseCase) : OnboardingAction() + object ResetUseCase : OnboardingAction() data class UpdateSignMode(val signMode: SignMode) : OnboardingAction() data class LoginWithToken(val loginToken: String) : OnboardingAction() data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction() diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 5e0c294b3e..3ae0ecccd2 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -126,6 +126,7 @@ class OnboardingViewModel @AssistedInject constructor( is OnboardingAction.OnGetStarted -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow) is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow) is OnboardingAction.UpdateUseCase -> handleUpdateUseCase() + OnboardingAction.ResetUseCase -> resetUseCase() is OnboardingAction.UpdateServerType -> handleUpdateServerType(action) is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action) is OnboardingAction.InitWith -> handleInitWith(action) @@ -462,6 +463,10 @@ class OnboardingViewModel @AssistedInject constructor( _viewEvents.post(OnboardingViewEvents.OpenServerSelection) } + private fun resetUseCase() { + // TODO remove stored use case + } + private fun handleUpdateServerType(action: OnboardingAction.UpdateServerType) { setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt index 5df10b7f5e..d86eb8a508 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthUseCaseFragment.kt @@ -60,7 +60,7 @@ class FtueAuthUseCaseFragment @Inject constructor() : AbstractFtueAuthFragment Date: Thu, 13 Jan 2022 10:36:42 +0000 Subject: [PATCH 33/72] making the text only buttons a button widget to give them touch feedback --- vector/src/main/res/layout/fragment_ftue_auth_splash.xml | 2 +- vector/src/main/res/layout/fragment_ftue_auth_use_case.xml | 4 ++-- vector/src/main/res/layout/fragment_ftue_splash_carousel.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/layout/fragment_ftue_auth_splash.xml b/vector/src/main/res/layout/fragment_ftue_auth_splash.xml index 39c0ad3007..803ad700db 100644 --- a/vector/src/main/res/layout/fragment_ftue_auth_splash.xml +++ b/vector/src/main/res/layout/fragment_ftue_auth_splash.xml @@ -190,7 +190,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/loginSplashSpace4" /> - - - Date: Thu, 13 Jan 2022 10:39:34 +0000 Subject: [PATCH 34/72] adding text to design preview for programatically set text --- vector/src/main/res/layout/fragment_ftue_auth_use_case.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml b/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml index 89d059dab9..33737c0f1e 100644 --- a/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml +++ b/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml @@ -1,6 +1,7 @@ + app:layout_constraintTop_toBottomOf="@id/useCaseOptionThree" + tools:text="Not sure yet? You can skip this question" /> Date: Thu, 13 Jan 2022 10:57:04 +0000 Subject: [PATCH 35/72] moving the work in progress strings out of the production strings files to avoid translations before they're signed off --- vector/src/main/res/values/donottranslate.xml | 11 +++++++++++ vector/src/main/res/values/strings.xml | 10 ---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/vector/src/main/res/values/donottranslate.xml b/vector/src/main/res/values/donottranslate.xml index d22391ed8c..3a9a60f912 100755 --- a/vector/src/main/res/values/donottranslate.xml +++ b/vector/src/main/res/values/donottranslate.xml @@ -14,4 +14,15 @@ Not implemented yet in ${app_name} + + Who will you chat to the most? + We\'ll help you get connected. + Friends and family + Teams + Communities + Not sure yet? %s + You can skip this question + Looking to join an existing server? + Connect to server + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 6d9684352b..c9adbc9d4a 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2528,16 +2528,6 @@ Cut the slack from teams. As universal as email, Element is a completely new type of collaboration. - Who will you chat to the most? - We\'ll help you get connected. - Friends and family - Teams - Communities - Not sure yet? %s - You can skip this question - Looking to join an existing server? - Connect to server - It\'s your conversation. Own it. Chat with people directly or in groups Keep conversations private with encryption From b371e24d9fd5ef88c976191134db7e0ddde4d820 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 13 Jan 2022 12:00:59 +0000 Subject: [PATCH 36/72] wrapping the use case into a scrollview to avoid overlapping on smaller devices --- .../layout/fragment_ftue_auth_use_case.xml | 320 +++++++++--------- 1 file changed, 166 insertions(+), 154 deletions(-) diff --git a/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml b/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml index 33737c0f1e..594fc80696 100644 --- a/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml +++ b/vector/src/main/res/layout/fragment_ftue_auth_use_case.xml @@ -1,169 +1,181 @@ - + android:fillViewport="true" + android:paddingTop="0dp" + android:paddingBottom="0dp"> - - - - - - - - - - - - - + android:paddingTop="@dimen/layout_vertical_margin" + android:paddingBottom="@dimen/layout_vertical_margin"> - + - + - + - + - + + + + + + + + + + + + + + + + +