diff --git a/CHANGES.md b/CHANGES.md index 841babee7e..5b3c34d9a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,9 @@ Improvements 🙌: - Small optimisation of scrolling experience in timeline (#2114) - Allow user to reset cross signing if he has no way to recover (#2052) - Create home shortcut for any room (#1525) + - Add a menu item to open the setting in room list and in room (#2171) + - Add a menu item in the timeline as a shortcut to invite user (#2171) + - Drawer: move settings access and add sign out action (#2171) - Filter room member (and banned users) by name (#2184) Bugfix 🐛: diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index eb024c4db1..829ab1624b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -305,6 +305,10 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet navigator.openRoomsFiltering(this) return true } + R.id.menu_home_setting -> { + navigator.openSettings(this) + return true + } } return super.onOptionsItemSelected(item) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt index 3344989b0a..b8eb054076 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt @@ -18,18 +18,22 @@ package im.vector.app.features.home import android.os.Bundle import android.view.View +import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.extensions.observeK import im.vector.app.core.extensions.replaceChildFragment import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.features.grouplist.GroupListFragment +import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.workers.signout.SignOutUiWorker +import kotlinx.android.synthetic.main.fragment_home_drawer.* import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.toMatrixItem -import kotlinx.android.synthetic.main.fragment_home_drawer.* import javax.inject.Inject class HomeDrawerFragment @Inject constructor( private val session: Session, + private val vectorPreferences: VectorPreferences, private val avatarRenderer: AvatarRenderer ) : VectorBaseFragment() { @@ -53,12 +57,19 @@ class HomeDrawerFragment @Inject constructor( homeDrawerUserIdView.text = user.userId } } + // Settings homeDrawerHeaderSettingsView.debouncedClicks { sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer) navigator.openSettings(requireActivity()) } + // Sign out + homeDrawerHeaderSignoutView.debouncedClicks { + sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer) + SignOutUiWorker(requireActivity()).perform() + } // Debug menu + homeDrawerHeaderDebugView.isVisible = vectorPreferences.developerMode() homeDrawerHeaderDebugView.debouncedClicks { sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer) navigator.openDebug(requireActivity()) 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 2fc2fe94a5..cca7e9e542 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 @@ -655,6 +655,14 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.ClearSendQueue) true } + R.id.invite -> { + navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId) + true + } + R.id.timeline_setting -> { + navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) + true + } R.id.resend_all -> { roomDetailViewModel.handle(RoomDetailAction.ResendAll) true diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index ce48d8ff89..871a550bd6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -180,11 +180,13 @@ class RoomDetailViewModel @AssistedInject constructor( PowerLevelsObservableFactory(room).createObservable() .subscribe { val canSendMessage = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) + val canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId) val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId) val isAllowedToStartWebRTCCall = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE) setState { copy( canSendMessage = canSendMessage, + canInvite = canInvite, isAllowedToManageWidgets = isAllowedToManageWidgets, isAllowedToStartWebRTCCall = isAllowedToStartWebRTCCall ) @@ -538,6 +540,8 @@ class RoomDetailViewModel @AssistedInject constructor( // For now always disable when not in developer mode, worker cancellation is not working properly timeline.pendingEventCount() > 0 && vectorPreferences.developerMode() R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true + R.id.timeline_setting -> true + R.id.invite -> state.canInvite R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true R.id.open_matrix_apps -> true R.id.voice_call, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index c443d61252..ffa56b8219 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -72,6 +72,7 @@ data class RoomDetailViewState( val canShowJumpToReadMarker: Boolean = true, val changeMembershipState: ChangeMembershipState = ChangeMembershipState.Unknown, val canSendMessage: Boolean = true, + val canInvite: Boolean = true, val isAllowedToManageWidgets: Boolean = false, val isAllowedToStartWebRTCCall: Boolean = true ) : MvRxState { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index f538c4993b..6c0aefe9da 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -259,8 +259,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() { findPreference("SETTINGS_SIGN_OUT_KEY")!! .onPreferenceClickListener = Preference.OnPreferenceClickListener { activity?.let { - SignOutUiWorker(requireActivity()) - .perform(requireContext()) + SignOutUiWorker(requireActivity()).perform() } false diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutBottomSheetActionButton.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetActionButton.kt similarity index 88% rename from vector/src/main/java/im/vector/app/features/workers/signout/SignoutBottomSheetActionButton.kt rename to vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetActionButton.kt index 02ea2569e8..3b73455923 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutBottomSheetActionButton.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetActionButton.kt @@ -31,7 +31,7 @@ import im.vector.app.R import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.themes.ThemeUtils -class SignoutBottomSheetActionButton @JvmOverloads constructor( +class SignOutBottomSheetActionButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : LinearLayout(context, attrs, defStyleAttr) { @@ -80,11 +80,11 @@ class SignoutBottomSheetActionButton @JvmOverloads constructor( inflate(context, R.layout.item_signout_action, this) ButterKnife.bind(this) - val typedArray = context.obtainStyledAttributes(attrs, R.styleable.SignoutBottomSheetActionButton, 0, 0) - title = typedArray.getString(R.styleable.SignoutBottomSheetActionButton_actionTitle) ?: "" - leftIcon = typedArray.getDrawable(R.styleable.SignoutBottomSheetActionButton_leftIcon) - tint = typedArray.getColor(R.styleable.SignoutBottomSheetActionButton_iconTint, ThemeUtils.getColor(context, android.R.attr.textColor)) - textColor = typedArray.getColor(R.styleable.SignoutBottomSheetActionButton_textColor, ThemeUtils.getColor(context, android.R.attr.textColor)) + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.SignOutBottomSheetActionButton, 0, 0) + title = typedArray.getString(R.styleable.SignOutBottomSheetActionButton_actionTitle) ?: "" + leftIcon = typedArray.getDrawable(R.styleable.SignOutBottomSheetActionButton_leftIcon) + tint = typedArray.getColor(R.styleable.SignOutBottomSheetActionButton_iconTint, ThemeUtils.getColor(context, android.R.attr.textColor)) + textColor = typedArray.getColor(R.styleable.SignOutBottomSheetActionButton_textColor, ThemeUtils.getColor(context, android.R.attr.textColor)) typedArray.recycle() diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt index ac4d495a8c..30f7ed93a3 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt @@ -35,8 +35,6 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import im.vector.app.R import im.vector.app.core.di.ScreenComponent import im.vector.app.core.dialogs.ExportKeysDialog @@ -45,6 +43,9 @@ import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity import im.vector.app.features.crypto.recover.BootstrapBottomSheet import im.vector.app.features.crypto.recover.SetupMode +import kotlinx.android.synthetic.main.bottom_sheet_logout_and_backup.* +import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import timber.log.Timber import javax.inject.Inject @@ -57,21 +58,6 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(), @BindView(R.id.bottom_sheet_signout_backingup_status_group) lateinit var backingUpStatusGroup: ViewGroup - @BindView(R.id.setupRecoveryButton) - lateinit var setupRecoveryButton: SignoutBottomSheetActionButton - - @BindView(R.id.setupMegolmBackupButton) - lateinit var setupMegolmBackupButton: SignoutBottomSheetActionButton - - @BindView(R.id.exportManuallyButton) - lateinit var exportManuallyButton: SignoutBottomSheetActionButton - - @BindView(R.id.exitAnywayButton) - lateinit var exitAnywayButton: SignoutBottomSheetActionButton - - @BindView(R.id.signOutButton) - lateinit var signOutButton: SignoutBottomSheetActionButton - @BindView(R.id.bottom_sheet_signout_icon_progress_bar) lateinit var backupProgress: ProgressBar @@ -138,6 +124,10 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(), } } + signOutButton.action = { + onSignOut?.run() + } + exportManuallyButton.action = { withState(viewModel) { state -> queryExportKeys(state.userId, QUERY_EXPORT_KEYS) @@ -182,10 +172,10 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(), // we should show option to setup 4S setupRecoveryButton.isVisible = true setupMegolmBackupButton.isVisible = false - signOutButton.isVisible = false // We let the option to ignore and quit exportManuallyButton.isVisible = true exitAnywayButton.isVisible = true + signOutButton.isVisible = false } else if (state.keysBackupState == KeysBackupState.Unknown || state.keysBackupState == KeysBackupState.Disabled) { sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_no_backup) backingUpStatusGroup.isVisible = false @@ -194,10 +184,10 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(), // we should show option to setup 4S setupRecoveryButton.isVisible = false setupMegolmBackupButton.isVisible = true - signOutButton.isVisible = false // We let the option to ignore and quit exportManuallyButton.isVisible = true exitAnywayButton.isVisible = true + signOutButton.isVisible = false } else { // so keybackup is setup // You should wait until all are uploaded @@ -213,13 +203,14 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(), backupCompleteImage.isVisible = true backupStatusTex.text = getString(R.string.keys_backup_info_keys_all_backup_up) - hideViews(setupMegolmBackupButton, exportManuallyButton, exitAnywayButton) + setupMegolmBackupButton.isVisible = false + exportManuallyButton.isVisible = false + exitAnywayButton.isVisible = false // You can signout signOutButton.isVisible = true } - KeysBackupState.WillBackUp, - KeysBackupState.BackingUp -> { + KeysBackupState.BackingUp -> { sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backing_up) // save in progress @@ -228,18 +219,21 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(), backupCompleteImage.isVisible = false backupStatusTex.text = getString(R.string.sign_out_bottom_sheet_backing_up_keys) - hideViews(setupMegolmBackupButton, setupMegolmBackupButton, signOutButton, exportManuallyButton) + setupMegolmBackupButton.isVisible = false + exportManuallyButton.isVisible = false exitAnywayButton.isVisible = true + signOutButton.isVisible = false } - KeysBackupState.NotTrusted -> { + KeysBackupState.NotTrusted -> { sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backup_not_active) // It's not trusted and we know there are unsaved keys.. backingUpStatusGroup.isVisible = false - exportManuallyButton.isVisible = true // option to enter pass/key setupMegolmBackupButton.isVisible = true + exportManuallyButton.isVisible = true exitAnywayButton.isVisible = true + signOutButton.isVisible = false } else -> { // mmm.. strange state @@ -253,21 +247,23 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(), when (state.hasBeenExportedToFile) { is Loading -> { signoutExportingLoading.isVisible = true - hideViews(setupRecoveryButton, - setupMegolmBackupButton, - exportManuallyButton, - backingUpStatusGroup, - signOutButton) + backingUpStatusGroup.isVisible = false + + setupRecoveryButton.isVisible = false + setupMegolmBackupButton.isVisible = false + exportManuallyButton.isVisible = false exitAnywayButton.isVisible = true + signOutButton.isVisible = false } is Success -> { if (state.hasBeenExportedToFile.invoke()) { sheetTitle.text = getString(R.string.action_sign_out_confirmation_simple) - hideViews(setupRecoveryButton, - setupMegolmBackupButton, - exportManuallyButton, - backingUpStatusGroup, - exitAnywayButton) + backingUpStatusGroup.isVisible = false + + setupRecoveryButton.isVisible = false + setupMegolmBackupButton.isVisible = false + exportManuallyButton.isVisible = false + exitAnywayButton.isVisible = false signOutButton.isVisible = true } } @@ -315,8 +311,4 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(), } } } - - private fun hideViews(vararg views: View) { - views.forEach { it.isVisible = false } - } } diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutUiWorker.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutUiWorker.kt index d78b652113..53ea5c7887 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutUiWorker.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutUiWorker.kt @@ -16,11 +16,9 @@ package im.vector.app.features.workers.signout -import android.content.Context import androidx.appcompat.app.AlertDialog import androidx.fragment.app.FragmentActivity import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.cannotLogoutSafely import im.vector.app.core.extensions.vectorComponent import im.vector.app.features.MainActivity @@ -28,11 +26,8 @@ import im.vector.app.features.MainActivityArgs class SignOutUiWorker(private val activity: FragmentActivity) { - lateinit var activeSessionHolder: ActiveSessionHolder - - fun perform(context: Context) { - activeSessionHolder = context.vectorComponent().activeSessionHolder() - val session = activeSessionHolder.getActiveSession() + fun perform() { + val session = activity.vectorComponent().activeSessionHolder().getSafeActiveSession() ?: return if (session.cannotLogoutSafely()) { // The backup check on logout flow has to be displayed if there are keys in the store, and the keys backup state is not Ready val signOutDialog = SignOutBottomSheetDialogFragment.newInstance() diff --git a/vector/src/main/res/drawable/ic_settings_18dp.xml b/vector/src/main/res/drawable/ic_settings_18dp.xml new file mode 100644 index 0000000000..4ca1725703 --- /dev/null +++ b/vector/src/main/res/drawable/ic_settings_18dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/drawable/ic_signout_18dp.xml b/vector/src/main/res/drawable/ic_signout_18dp.xml new file mode 100644 index 0000000000..80f2995bc0 --- /dev/null +++ b/vector/src/main/res/drawable/ic_signout_18dp.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/vector/src/main/res/layout/bottom_sheet_logout_and_backup.xml b/vector/src/main/res/layout/bottom_sheet_logout_and_backup.xml index c109e821cb..63a250e219 100644 --- a/vector/src/main/res/layout/bottom_sheet_logout_and_backup.xml +++ b/vector/src/main/res/layout/bottom_sheet_logout_and_backup.xml @@ -81,7 +81,7 @@ android:layout_height="wrap_content" /> - - - - - @@ -42,11 +43,12 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="24dp" + android:layout_marginEnd="@dimen/layout_horizontal_margin" android:maxLines="1" android:singleLine="true" android:textColor="?riotx_text_primary" android:textSize="15sp" - app:layout_constraintEnd_toStartOf="@id/homeDrawerHeaderSettingsView" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView" app:layout_constraintTop_toBottomOf="@+id/homeDrawerHeaderAvatarView" tools:text="@sample/matrix.json/data/displayName" /> @@ -55,39 +57,71 @@ android:id="@+id/homeDrawerUserIdView" android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/layout_horizontal_margin" android:layout_marginBottom="17dp" android:maxLines="1" android:singleLine="true" android:textColor="?riotx_text_secondary" android:textSize="15sp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@id/homeDrawerHeaderSettingsView" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView" app:layout_constraintTop_toBottomOf="@+id/homeDrawerUsernameView" tools:text="@sample/matrix.json/data/mxid" /> - - + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/menu/home.xml b/vector/src/main/res/menu/home.xml index db84db6622..7a77c45240 100644 --- a/vector/src/main/res/menu/home.xml +++ b/vector/src/main/res/menu/home.xml @@ -2,6 +2,12 @@ + + \ No newline at end of file diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml index e0566ccf6d..86ffe1f724 100644 --- a/vector/src/main/res/menu/menu_timeline.xml +++ b/vector/src/main/res/menu/menu_timeline.xml @@ -3,11 +3,22 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> + + + + - +