diff --git a/CHANGES.md b/CHANGES.md index d2767d0174..4049ef2add 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,12 +12,16 @@ Improvements 🙌: - Allow user to reset cross signing if he has no way to recover (#2052) - Create home shortcut for any room (#1525) - Can't confirm email due to killing by Android (#2021) + - 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 🐛: - Improve support for image/audio/video/file selection with intent changes (#1376) - Fix Splash layout on small screens - Invalid popup when pressing back (#1635) + - Simplifies draft management and should fix bunch of draft issues (#952, #683) Translations 🗣: - diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt index 0717a10d03..2bb6c0ff69 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt @@ -101,8 +101,11 @@ class RxRoom(private val room: Room) { return room.getEventReadReceiptsLive(eventId).asObservable() } - fun liveDrafts(): Observable> { - return room.getDraftsLive().asObservable() + fun liveDraft(): Observable> { + return room.getDraftLive().asObservable() + .startWithCallable { + room.getDraft().toOptional() + } } fun liveNotificationState(): Observable { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt index dc91d5177f..6ef73d6486 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt @@ -20,6 +20,7 @@ package org.matrix.android.sdk.api.session.room.send import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.Optional interface DraftService { @@ -34,8 +35,12 @@ interface DraftService { fun deleteDraft(callback: MatrixCallback): Cancelable /** - * Return the current drafts if any, as a live data - * The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts + * Return the current draft or null */ - fun getDraftsLive(): LiveData> + fun getDraft(): UserDraft? + + /** + * Return the current draft if any, as a live data + */ + fun getDraftLive(): LiveData> } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt index dafa7df0eb..0426ee13e1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.send.DraftService import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers @@ -55,7 +56,11 @@ internal class DefaultDraftService @AssistedInject constructor(@Assisted private } } - override fun getDraftsLive(): LiveData> { + override fun getDraft(): UserDraft? { + return draftRepository.getDraft(roomId) + } + + override fun getDraftLive(): LiveData> { return draftRepository.getDraftsLive(roomId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt index bc50b2d990..5cc6c990d9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt @@ -20,43 +20,67 @@ package org.matrix.android.sdk.internal.session.room.draft import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.kotlin.createObject import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.session.room.send.UserDraft +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.mapper.DraftMapper -import org.matrix.android.sdk.internal.database.model.DraftEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.UserDraftsEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.util.awaitTransaction -import io.realm.Realm -import io.realm.kotlin.createObject import timber.log.Timber import javax.inject.Inject -class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy) { +internal class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider) { suspend fun saveDraft(roomId: String, userDraft: UserDraft) { monarchy.awaitTransaction { - saveDraft(it, userDraft, roomId) + saveDraftInDb(it, userDraft, roomId) } } suspend fun deleteDraft(roomId: String) { monarchy.awaitTransaction { - deleteDraft(it, roomId) + deleteDraftFromDb(it, roomId) } } - private fun deleteDraft(realm: Realm, roomId: String) { - UserDraftsEntity.where(realm, roomId).findFirst()?.let { userDraftsEntity -> - if (userDraftsEntity.userDrafts.isNotEmpty()) { - userDraftsEntity.userDrafts.removeAt(userDraftsEntity.userDrafts.size - 1) - } + fun getDraft(roomId: String): UserDraft? { + return realmSessionProvider.withRealm { realm -> + UserDraftsEntity.where(realm, roomId).findFirst() + ?.userDrafts + ?.firstOrNull() + ?.let { + DraftMapper.map(it) + } } } - private fun saveDraft(realm: Realm, draft: UserDraft, roomId: String) { + fun getDraftsLive(roomId: String): LiveData> { + val liveData = monarchy.findAllMappedWithChanges( + { UserDraftsEntity.where(it, roomId) }, + { + it.userDrafts.map { draft -> + DraftMapper.map(draft) + } + } + ) + return Transformations.map(liveData) { + it.firstOrNull()?.firstOrNull().toOptional() + } + } + + private fun deleteDraftFromDb(realm: Realm, roomId: String) { + UserDraftsEntity.where(realm, roomId).findFirst()?.userDrafts?.clear() + } + + private fun saveDraftInDb(realm: Realm, draft: UserDraft, roomId: String) { val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) @@ -68,62 +92,15 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: userDraftsEntity.let { userDraftEntity -> // Save only valid draft if (draft.isValid()) { - // Add a new draft or update the current one? + // Replace the current draft val newDraft = DraftMapper.map(draft) - - // Is it an update of the top draft? - val topDraft = userDraftEntity.userDrafts.lastOrNull() - - if (topDraft == null) { - Timber.d("Draft: create a new draft ${privacySafe(draft)}") - userDraftEntity.userDrafts.add(newDraft) - } else if (topDraft.draftMode == DraftEntity.MODE_EDIT) { - // top draft is an edit - if (newDraft.draftMode == DraftEntity.MODE_EDIT) { - if (topDraft.linkedEventId == newDraft.linkedEventId) { - // Update the top draft - Timber.d("Draft: update the top edit draft ${privacySafe(draft)}") - topDraft.content = newDraft.content - } else { - // Check a previously EDIT draft with the same id - val existingEditDraftOfSameEvent = userDraftEntity.userDrafts.find { - it.draftMode == DraftEntity.MODE_EDIT && it.linkedEventId == newDraft.linkedEventId - } - - if (existingEditDraftOfSameEvent != null) { - // Ignore the new text, restore what was typed before, by putting the draft to the top - Timber.d("Draft: restore a previously edit draft ${privacySafe(draft)}") - userDraftEntity.userDrafts.remove(existingEditDraftOfSameEvent) - userDraftEntity.userDrafts.add(existingEditDraftOfSameEvent) - } else { - Timber.d("Draft: add a new edit draft ${privacySafe(draft)}") - userDraftEntity.userDrafts.add(newDraft) - } - } - } else { - // Add a new regular draft to the top - Timber.d("Draft: add a new draft ${privacySafe(draft)}") - userDraftEntity.userDrafts.add(newDraft) - } - } else { - // Top draft is not an edit - if (newDraft.draftMode == DraftEntity.MODE_EDIT) { - Timber.d("Draft: create a new edit draft ${privacySafe(draft)}") - userDraftEntity.userDrafts.add(newDraft) - } else { - // Update the top draft - Timber.d("Draft: update the top draft ${privacySafe(draft)}") - topDraft.draftMode = newDraft.draftMode - topDraft.content = newDraft.content - topDraft.linkedEventId = newDraft.linkedEventId - } - } + Timber.d("Draft: create a new draft ${privacySafe(draft)}") + userDraftEntity.userDrafts.clear() + userDraftEntity.userDrafts.add(newDraft) } else { // There is no draft to save, so the composer was clear Timber.d("Draft: delete a draft") - val topDraft = userDraftEntity.userDrafts.lastOrNull() - if (topDraft == null) { Timber.d("Draft: nothing to do") } else { @@ -135,20 +112,6 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: } } - fun getDraftsLive(roomId: String): LiveData> { - val liveData = monarchy.findAllMappedWithChanges( - { UserDraftsEntity.where(it, roomId) }, - { - it.userDrafts.map { draft -> - DraftMapper.map(draft) - } - } - ) - return Transformations.map(liveData) { - it.firstOrNull().orEmpty() - } - } - private fun privacySafe(o: Any): Any { if (BuildConfig.LOG_PRIVATE_DATA) { return o 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/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index 4bdf2e7e57..5f851b8da7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -51,7 +51,7 @@ sealed class RoomDetailAction : VectorViewModelAction { data class EnterEditMode(val eventId: String, val text: String) : RoomDetailAction() data class EnterQuoteMode(val eventId: String, val text: String) : RoomDetailAction() data class EnterReplyMode(val eventId: String, val text: String) : RoomDetailAction() - data class ExitSpecialMode(val text: String) : RoomDetailAction() + data class EnterRegularMode(val text: String, val fromSharing: Boolean) : RoomDetailAction() data class ResendMessage(val eventId: String) : RoomDetailAction() data class RemoveFailedEcho(val eventId: String) : RoomDetailAction() 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 51aeda2aab..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 @@ -485,8 +485,7 @@ class RoomDetailFragment @Inject constructor( if (savedInstanceState == null) { when (val sharedData = roomDetailArgs.sharedData) { is SharedData.Text -> { - // Save a draft to set the shared text to the composer - roomDetailViewModel.handle(RoomDetailAction.SaveDraft(sharedData.text)) + roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true)) } is SharedData.Attachments -> { // open share edition @@ -656,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 @@ -1014,7 +1021,7 @@ class RoomDetailFragment @Inject constructor( } override fun onCloseRelatedMessage() { - roomDetailViewModel.handle(RoomDetailAction.ExitSpecialMode(composerLayout.text.toString())) + roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(composerLayout.text.toString(), false)) } override fun onRichContentSelected(contentUri: Uri): Boolean { @@ -1147,12 +1154,8 @@ class RoomDetailFragment @Inject constructor( private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) { when (sendMessageResult) { - is RoomDetailViewEvents.MessageSent -> { - updateComposerText("") - } is RoomDetailViewEvents.SlashCommandHandled -> { sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } - updateComposerText("") } is RoomDetailViewEvents.SlashCommandError -> { displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) 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 1b5e928843..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 @@ -164,7 +164,7 @@ class RoomDetailViewModel @AssistedInject constructor( getUnreadState() observeSyncState() observeEventDisplayedActions() - observeDrafts() + getDraftIfAny() observeUnreadState() observeMyRoomMember() observeActiveRoomWidgets() @@ -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 ) @@ -226,52 +228,52 @@ class RoomDetailViewModel @AssistedInject constructor( override fun handle(action: RoomDetailAction) { when (action) { - is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) - is RoomDetailAction.SaveDraft -> handleSaveDraft(action) - is RoomDetailAction.SendMessage -> handleSendMessage(action) - is RoomDetailAction.SendMedia -> handleSendMedia(action) - is RoomDetailAction.SendSticker -> handleSendSticker(action) - is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) - is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) - is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) - is RoomDetailAction.SendReaction -> handleSendReaction(action) - is RoomDetailAction.AcceptInvite -> handleAcceptInvite() - is RoomDetailAction.RejectInvite -> handleRejectInvite() - is RoomDetailAction.RedactAction -> handleRedactEvent(action) - is RoomDetailAction.UndoReaction -> handleUndoReact(action) - is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) - is RoomDetailAction.ExitSpecialMode -> handleExitSpecialMode(action) - is RoomDetailAction.EnterEditMode -> handleEditAction(action) - is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) - is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) - is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action) - is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) - is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) - is RoomDetailAction.ResendMessage -> handleResendEvent(action) - is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) - is RoomDetailAction.ClearSendQueue -> handleClearSendQueue() - is RoomDetailAction.ResendAll -> handleResendAll() - is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() - is RoomDetailAction.ReportContent -> handleReportContent(action) - is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) + is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) + is RoomDetailAction.SaveDraft -> handleSaveDraft(action) + is RoomDetailAction.SendMessage -> handleSendMessage(action) + is RoomDetailAction.SendMedia -> handleSendMedia(action) + is RoomDetailAction.SendSticker -> handleSendSticker(action) + is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) + is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) + is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) + is RoomDetailAction.SendReaction -> handleSendReaction(action) + is RoomDetailAction.AcceptInvite -> handleAcceptInvite() + is RoomDetailAction.RejectInvite -> handleRejectInvite() + is RoomDetailAction.RedactAction -> handleRedactEvent(action) + is RoomDetailAction.UndoReaction -> handleUndoReact(action) + is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) + is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action) + is RoomDetailAction.EnterEditMode -> handleEditAction(action) + is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) + is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) + is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action) + is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) + is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) + is RoomDetailAction.ResendMessage -> handleResendEvent(action) + is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) + is RoomDetailAction.ClearSendQueue -> handleClearSendQueue() + is RoomDetailAction.ResendAll -> handleResendAll() + is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() + is RoomDetailAction.ReportContent -> handleReportContent(action) + is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages() - is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() - is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) - is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) - is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) - is RoomDetailAction.RequestVerification -> handleRequestVerification(action) - is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) - is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) - is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) - is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() - is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() - is RoomDetailAction.StartCall -> handleStartCall(action) - is RoomDetailAction.EndCall -> handleEndCall() - is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() - is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) - is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) - is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) - is RoomDetailAction.CancelSend -> handleCancel(action) + is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() + is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) + is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) + is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) + is RoomDetailAction.RequestVerification -> handleRequestVerification(action) + is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) + is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) + is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) + is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() + is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() + is RoomDetailAction.StartCall -> handleStartCall(action) + is RoomDetailAction.EndCall -> handleEndCall() + is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() + is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) + is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) + is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) + is RoomDetailAction.CancelSend -> handleCancel(action) }.exhaustive } @@ -449,47 +451,52 @@ class RoomDetailViewModel @AssistedInject constructor( /** * Convert a send mode to a draft and save the draft */ - private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) { - withState { - when (it.sendMode) { - is SendMode.REGULAR -> room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback()) - is SendMode.REPLY -> room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback()) - is SendMode.QUOTE -> room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback()) - is SendMode.EDIT -> room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback()) - }.exhaustive + private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) = withState { + when { + it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> { + setState { copy(sendMode = it.sendMode.copy(action.draft)) } + room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback()) + } + it.sendMode is SendMode.REPLY -> { + setState { copy(sendMode = it.sendMode.copy(text = action.draft)) } + room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback()) + } + it.sendMode is SendMode.QUOTE -> { + setState { copy(sendMode = it.sendMode.copy(text = action.draft)) } + room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback()) + } + it.sendMode is SendMode.EDIT -> { + setState { copy(sendMode = it.sendMode.copy(text = action.draft)) } + room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback()) + } } } - private fun observeDrafts() { - room.rx().liveDrafts() - .subscribe { - Timber.d("Draft update --> SetState") - setState { - val draft = it.lastOrNull() ?: UserDraft.REGULAR("") - copy( - // Create a sendMode from a draft and retrieve the TimelineEvent - sendMode = when (draft) { - is UserDraft.REGULAR -> SendMode.REGULAR(draft.text) - is UserDraft.QUOTE -> { - room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> - SendMode.QUOTE(timelineEvent, draft.text) - } - } - is UserDraft.REPLY -> { - room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> - SendMode.REPLY(timelineEvent, draft.text) - } - } - is UserDraft.EDIT -> { - room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> - SendMode.EDIT(timelineEvent, draft.text) - } - } - } ?: SendMode.REGULAR("") - ) - } - } - .disposeOnClear() + private fun getDraftIfAny() { + val currentDraft = room.getDraft() ?: return + setState { + copy( + // Create a sendMode from a draft and retrieve the TimelineEvent + sendMode = when (currentDraft) { + is UserDraft.REGULAR -> SendMode.REGULAR(currentDraft.text, false) + is UserDraft.QUOTE -> { + room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent -> + SendMode.QUOTE(timelineEvent, currentDraft.text) + } + } + is UserDraft.REPLY -> { + room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent -> + SendMode.REPLY(timelineEvent, currentDraft.text) + } + } + is UserDraft.EDIT -> { + room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent -> + SendMode.EDIT(timelineEvent, currentDraft.text) + } + } + } ?: SendMode.REGULAR("", fromSharing = false) + ) + } } private fun handleUserIsTyping(action: RoomDetailAction.UserIsTyping) { @@ -533,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, @@ -680,7 +689,7 @@ class RoomDetailViewModel @AssistedInject constructor( } }.exhaustive } - is SendMode.EDIT -> { + is SendMode.EDIT -> { // is original event a reply? val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel()?.relatesTo?.inReplyTo?.eventId ?: state.sendMode.timelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId @@ -706,7 +715,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } - is SendMode.QUOTE -> { + is SendMode.QUOTE -> { val messageContent: MessageContent? = state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() ?: state.sendMode.timelineEvent.root.getClearContent().toModel() @@ -729,7 +738,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } - is SendMode.REPLY -> { + is SendMode.REPLY -> { state.sendMode.timelineEvent.let { room.replyToMessage(it, action.text.toString(), action.autoMarkdown) _viewEvents.post(RoomDetailViewEvents.MessageSent) @@ -740,8 +749,15 @@ class RoomDetailViewModel @AssistedInject constructor( } } - private fun popDraft() { - room.deleteDraft(NoOpMatrixCallback()) + private fun popDraft() = withState { + if (it.sendMode is SendMode.REGULAR && it.sendMode.fromSharing) { + // If we were sharing, we want to get back our last value from draft + getDraftIfAny() + } else { + // Otherwise we clear the composer and remove the draft from db + setState { copy(sendMode = SendMode.REGULAR("", false)) } + room.deleteDraft(NoOpMatrixCallback()) + } } private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) { @@ -915,74 +931,25 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleEditAction(action: RoomDetailAction.EnterEditMode) { - saveCurrentDraft(action.text) - room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> - setState { copy(sendMode = SendMode.EDIT(timelineEvent, action.text)) } - timelineEvent.root.eventId?.let { - room.saveDraft(UserDraft.EDIT(it, timelineEvent.getTextEditableContent() ?: ""), NoOpMatrixCallback()) - } + setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent() ?: "")) } } } private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) { - saveCurrentDraft(action.text) - room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) } - withState { state -> - // Save a new draft and keep the previously entered text, if it was not an edit - timelineEvent.root.eventId?.let { - if (state.sendMode is SendMode.EDIT) { - room.saveDraft(UserDraft.QUOTE(it, ""), NoOpMatrixCallback()) - } else { - room.saveDraft(UserDraft.QUOTE(it, action.text), NoOpMatrixCallback()) - } - } - } } } private fun handleReplyAction(action: RoomDetailAction.EnterReplyMode) { - saveCurrentDraft(action.text) - room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) } - withState { state -> - // Save a new draft and keep the previously entered text, if it was not an edit - timelineEvent.root.eventId?.let { - if (state.sendMode is SendMode.EDIT) { - room.saveDraft(UserDraft.REPLY(it, ""), NoOpMatrixCallback()) - } else { - room.saveDraft(UserDraft.REPLY(it, action.text), NoOpMatrixCallback()) - } - } - } } } - private fun saveCurrentDraft(draft: String) { - // Save the draft with the current text if any - withState { - if (draft.isNotBlank()) { - when (it.sendMode) { - is SendMode.REGULAR -> room.saveDraft(UserDraft.REGULAR(draft), NoOpMatrixCallback()) - is SendMode.REPLY -> room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback()) - is SendMode.QUOTE -> room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback()) - is SendMode.EDIT -> room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback()) - } - } - } - } - - private fun handleExitSpecialMode(action: RoomDetailAction.ExitSpecialMode) = withState { - if (it.sendMode is SendMode.EDIT) { - room.deleteDraft(NoOpMatrixCallback()) - } else { - // Save a new draft and keep the previously entered text - room.saveDraft(UserDraft.REGULAR(action.text), NoOpMatrixCallback()) - } - setState { copy(sendMode = SendMode.REGULAR(action.text)) } + private fun handleEnterRegularMode(action: RoomDetailAction.EnterRegularMode) = setState { + copy(sendMode = SendMode.REGULAR(action.text, action.fromSharing)) } private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) { 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 36acc35148..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 @@ -37,7 +37,12 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget * Depending on the state the bottom toolbar will change (icons/preview/actions...) */ sealed class SendMode(open val text: String) { - data class REGULAR(override val text: String) : SendMode(text) + data class REGULAR( + override val text: String, + val fromSharing: Boolean, + // This is necessary for forcing refresh on selectSubscribe + private val ts: Long = System.currentTimeMillis() + ) : SendMode(text) data class QUOTE(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text) data class EDIT(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text) data class REPLY(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text) @@ -58,7 +63,7 @@ data class RoomDetailViewState( val asyncRoomSummary: Async = Uninitialized, val activeRoomWidgets: Async> = Uninitialized, val typingMessage: String? = null, - val sendMode: SendMode = SendMode.REGULAR(""), + val sendMode: SendMode = SendMode.REGULAR("", false), val tombstoneEvent: Event? = null, val tombstoneEventHandling: Async = Uninitialized, val syncState: SyncState = SyncState.Idle, @@ -67,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/home/room/detail/RoomMessageTouchHelperCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt index ef13865374..41f386c606 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt @@ -25,12 +25,15 @@ import android.view.MotionEvent import android.view.View import androidx.annotation.DrawableRes import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_SWIPE import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyTouchHelperCallback import com.airbnb.epoxy.EpoxyViewHolder +import im.vector.app.R +import im.vector.app.features.themes.ThemeUtils import timber.log.Timber import kotlin.math.abs import kotlin.math.min @@ -52,7 +55,16 @@ class RoomMessageTouchHelperCallback(private val context: Context, private var replyButtonProgress: Float = 0F private var lastReplyButtonAnimationTime: Long = 0 - private var imageDrawable: Drawable = ContextCompat.getDrawable(context, actionIcon)!! + private val imageDrawable: Drawable = DrawableCompat.wrap( + ContextCompat.getDrawable(context, actionIcon)!! + ) + + init { + DrawableCompat.setTint( + imageDrawable, + ThemeUtils.getColor(context, R.attr.riotx_text_primary) + ) + } private val triggerDistance = convertToPx(100) private val minShowDistance = convertToPx(20) 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/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml index c844f10ed2..814ef645d7 100644 --- a/vector/src/main/res/layout/item_room.xml +++ b/vector/src/main/res/layout/item_room.xml @@ -105,6 +105,7 @@ android:layout_marginEnd="4dp" android:src="@drawable/ic_edit" android:visibility="gone" + android:tint="?riotx_text_primary" app:layout_constraintBottom_toBottomOf="@+id/roomNameView" app:layout_constraintEnd_toStartOf="@+id/roomUnreadCounterBadgeView" app:layout_constraintStart_toEndOf="@+id/roomNameView" 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"> + + + + - +