Merge branch 'develop' into feature/ons/fix_email_confirmation_flow

This commit is contained in:
Onuray Sahin 2020-10-06 19:00:19 +03:00 committed by GitHub
commit 7722e6d8e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 361 additions and 312 deletions

View File

@ -12,12 +12,16 @@ Improvements 🙌:
- Allow user to reset cross signing if he has no way to recover (#2052) - Allow user to reset cross signing if he has no way to recover (#2052)
- Create home shortcut for any room (#1525) - Create home shortcut for any room (#1525)
- Can't confirm email due to killing by Android (#2021) - 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) - Filter room member (and banned users) by name (#2184)
Bugfix 🐛: Bugfix 🐛:
- Improve support for image/audio/video/file selection with intent changes (#1376) - Improve support for image/audio/video/file selection with intent changes (#1376)
- Fix Splash layout on small screens - Fix Splash layout on small screens
- Invalid popup when pressing back (#1635) - Invalid popup when pressing back (#1635)
- Simplifies draft management and should fix bunch of draft issues (#952, #683)
Translations 🗣: Translations 🗣:
- -

View File

@ -101,8 +101,11 @@ class RxRoom(private val room: Room) {
return room.getEventReadReceiptsLive(eventId).asObservable() return room.getEventReadReceiptsLive(eventId).asObservable()
} }
fun liveDrafts(): Observable<List<UserDraft>> { fun liveDraft(): Observable<Optional<UserDraft>> {
return room.getDraftsLive().asObservable() return room.getDraftLive().asObservable()
.startWithCallable {
room.getDraft().toOptional()
}
} }
fun liveNotificationState(): Observable<RoomNotificationState> { fun liveNotificationState(): Observable<RoomNotificationState> {

View File

@ -20,6 +20,7 @@ package org.matrix.android.sdk.api.session.room.send
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional
interface DraftService { interface DraftService {
@ -34,8 +35,12 @@ interface DraftService {
fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable
/** /**
* Return the current drafts if any, as a live data * Return the current draft or null
* The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts
*/ */
fun getDraftsLive(): LiveData<List<UserDraft>> fun getDraft(): UserDraft?
/**
* Return the current draft if any, as a live data
*/
fun getDraftLive(): LiveData<Optional<UserDraft>>
} }

View File

@ -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.DraftService
import org.matrix.android.sdk.api.session.room.send.UserDraft 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.Cancelable
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
@ -55,7 +56,11 @@ internal class DefaultDraftService @AssistedInject constructor(@Assisted private
} }
} }
override fun getDraftsLive(): LiveData<List<UserDraft>> { override fun getDraft(): UserDraft? {
return draftRepository.getDraft(roomId)
}
override fun getDraftLive(): LiveData<Optional<UserDraft>> {
return draftRepository.getDraftsLive(roomId) return draftRepository.getDraftsLive(roomId)
} }
} }

View File

@ -20,43 +20,67 @@ package org.matrix.android.sdk.internal.session.room.draft
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy 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.BuildConfig
import org.matrix.android.sdk.api.session.room.send.UserDraft 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.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.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.UserDraftsEntity import org.matrix.android.sdk.internal.database.model.UserDraftsEntity
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.awaitTransaction
import io.realm.Realm
import io.realm.kotlin.createObject
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject 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) { suspend fun saveDraft(roomId: String, userDraft: UserDraft) {
monarchy.awaitTransaction { monarchy.awaitTransaction {
saveDraft(it, userDraft, roomId) saveDraftInDb(it, userDraft, roomId)
} }
} }
suspend fun deleteDraft(roomId: String) { suspend fun deleteDraft(roomId: String) {
monarchy.awaitTransaction { monarchy.awaitTransaction {
deleteDraft(it, roomId) deleteDraftFromDb(it, roomId)
} }
} }
private fun deleteDraft(realm: Realm, roomId: String) { fun getDraft(roomId: String): UserDraft? {
UserDraftsEntity.where(realm, roomId).findFirst()?.let { userDraftsEntity -> return realmSessionProvider.withRealm { realm ->
if (userDraftsEntity.userDrafts.isNotEmpty()) { UserDraftsEntity.where(realm, roomId).findFirst()
userDraftsEntity.userDrafts.removeAt(userDraftsEntity.userDrafts.size - 1) ?.userDrafts
?.firstOrNull()
?.let {
DraftMapper.map(it)
} }
} }
} }
private fun saveDraft(realm: Realm, draft: UserDraft, roomId: String) { fun getDraftsLive(roomId: String): LiveData<Optional<UserDraft>> {
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() val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId) ?: realm.createObject(roomId)
@ -68,62 +92,15 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy:
userDraftsEntity.let { userDraftEntity -> userDraftsEntity.let { userDraftEntity ->
// Save only valid draft // Save only valid draft
if (draft.isValid()) { if (draft.isValid()) {
// Add a new draft or update the current one? // Replace the current draft
val newDraft = DraftMapper.map(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)}") Timber.d("Draft: create a new draft ${privacySafe(draft)}")
userDraftEntity.userDrafts.clear()
userDraftEntity.userDrafts.add(newDraft) 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
}
}
} else { } else {
// There is no draft to save, so the composer was clear // There is no draft to save, so the composer was clear
Timber.d("Draft: delete a draft") Timber.d("Draft: delete a draft")
val topDraft = userDraftEntity.userDrafts.lastOrNull() val topDraft = userDraftEntity.userDrafts.lastOrNull()
if (topDraft == null) { if (topDraft == null) {
Timber.d("Draft: nothing to do") Timber.d("Draft: nothing to do")
} else { } else {
@ -135,20 +112,6 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy:
} }
} }
fun getDraftsLive(roomId: String): LiveData<List<UserDraft>> {
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 { private fun privacySafe(o: Any): Any {
if (BuildConfig.LOG_PRIVATE_DATA) { if (BuildConfig.LOG_PRIVATE_DATA) {
return o return o

View File

@ -305,6 +305,10 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
navigator.openRoomsFiltering(this) navigator.openRoomsFiltering(this)
return true return true
} }
R.id.menu_home_setting -> {
navigator.openSettings(this)
return true
}
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)

View File

@ -18,18 +18,22 @@ package im.vector.app.features.home
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.core.view.isVisible
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.observeK import im.vector.app.core.extensions.observeK
import im.vector.app.core.extensions.replaceChildFragment import im.vector.app.core.extensions.replaceChildFragment
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.grouplist.GroupListFragment 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.session.Session
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import kotlinx.android.synthetic.main.fragment_home_drawer.*
import javax.inject.Inject import javax.inject.Inject
class HomeDrawerFragment @Inject constructor( class HomeDrawerFragment @Inject constructor(
private val session: Session, private val session: Session,
private val vectorPreferences: VectorPreferences,
private val avatarRenderer: AvatarRenderer private val avatarRenderer: AvatarRenderer
) : VectorBaseFragment() { ) : VectorBaseFragment() {
@ -53,12 +57,19 @@ class HomeDrawerFragment @Inject constructor(
homeDrawerUserIdView.text = user.userId homeDrawerUserIdView.text = user.userId
} }
} }
// Settings
homeDrawerHeaderSettingsView.debouncedClicks { homeDrawerHeaderSettingsView.debouncedClicks {
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer) sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
navigator.openSettings(requireActivity()) navigator.openSettings(requireActivity())
} }
// Sign out
homeDrawerHeaderSignoutView.debouncedClicks {
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
SignOutUiWorker(requireActivity()).perform()
}
// Debug menu // Debug menu
homeDrawerHeaderDebugView.isVisible = vectorPreferences.developerMode()
homeDrawerHeaderDebugView.debouncedClicks { homeDrawerHeaderDebugView.debouncedClicks {
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer) sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
navigator.openDebug(requireActivity()) navigator.openDebug(requireActivity())

View File

@ -51,7 +51,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class EnterEditMode(val eventId: String, val text: String) : RoomDetailAction() data class EnterEditMode(val eventId: String, val text: String) : RoomDetailAction()
data class EnterQuoteMode(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 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 ResendMessage(val eventId: String) : RoomDetailAction()
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction() data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()

View File

@ -485,8 +485,7 @@ class RoomDetailFragment @Inject constructor(
if (savedInstanceState == null) { if (savedInstanceState == null) {
when (val sharedData = roomDetailArgs.sharedData) { when (val sharedData = roomDetailArgs.sharedData) {
is SharedData.Text -> { is SharedData.Text -> {
// Save a draft to set the shared text to the composer roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true))
roomDetailViewModel.handle(RoomDetailAction.SaveDraft(sharedData.text))
} }
is SharedData.Attachments -> { is SharedData.Attachments -> {
// open share edition // open share edition
@ -656,6 +655,14 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.ClearSendQueue) roomDetailViewModel.handle(RoomDetailAction.ClearSendQueue)
true true
} }
R.id.invite -> {
navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId)
true
}
R.id.timeline_setting -> {
navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId)
true
}
R.id.resend_all -> { R.id.resend_all -> {
roomDetailViewModel.handle(RoomDetailAction.ResendAll) roomDetailViewModel.handle(RoomDetailAction.ResendAll)
true true
@ -1014,7 +1021,7 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onCloseRelatedMessage() { override fun onCloseRelatedMessage() {
roomDetailViewModel.handle(RoomDetailAction.ExitSpecialMode(composerLayout.text.toString())) roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(composerLayout.text.toString(), false))
} }
override fun onRichContentSelected(contentUri: Uri): Boolean { override fun onRichContentSelected(contentUri: Uri): Boolean {
@ -1147,12 +1154,8 @@ class RoomDetailFragment @Inject constructor(
private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) { private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) {
when (sendMessageResult) { when (sendMessageResult) {
is RoomDetailViewEvents.MessageSent -> {
updateComposerText("")
}
is RoomDetailViewEvents.SlashCommandHandled -> { is RoomDetailViewEvents.SlashCommandHandled -> {
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
updateComposerText("")
} }
is RoomDetailViewEvents.SlashCommandError -> { is RoomDetailViewEvents.SlashCommandError -> {
displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))

View File

@ -164,7 +164,7 @@ class RoomDetailViewModel @AssistedInject constructor(
getUnreadState() getUnreadState()
observeSyncState() observeSyncState()
observeEventDisplayedActions() observeEventDisplayedActions()
observeDrafts() getDraftIfAny()
observeUnreadState() observeUnreadState()
observeMyRoomMember() observeMyRoomMember()
observeActiveRoomWidgets() observeActiveRoomWidgets()
@ -180,11 +180,13 @@ class RoomDetailViewModel @AssistedInject constructor(
PowerLevelsObservableFactory(room).createObservable() PowerLevelsObservableFactory(room).createObservable()
.subscribe { .subscribe {
val canSendMessage = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) 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 isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId)
val isAllowedToStartWebRTCCall = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE) val isAllowedToStartWebRTCCall = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE)
setState { setState {
copy( copy(
canSendMessage = canSendMessage, canSendMessage = canSendMessage,
canInvite = canInvite,
isAllowedToManageWidgets = isAllowedToManageWidgets, isAllowedToManageWidgets = isAllowedToManageWidgets,
isAllowedToStartWebRTCCall = isAllowedToStartWebRTCCall isAllowedToStartWebRTCCall = isAllowedToStartWebRTCCall
) )
@ -240,7 +242,7 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.RedactAction -> handleRedactEvent(action) is RoomDetailAction.RedactAction -> handleRedactEvent(action)
is RoomDetailAction.UndoReaction -> handleUndoReact(action) is RoomDetailAction.UndoReaction -> handleUndoReact(action)
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
is RoomDetailAction.ExitSpecialMode -> handleExitSpecialMode(action) is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
is RoomDetailAction.EnterEditMode -> handleEditAction(action) is RoomDetailAction.EnterEditMode -> handleEditAction(action)
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
@ -449,48 +451,53 @@ class RoomDetailViewModel @AssistedInject constructor(
/** /**
* Convert a send mode to a draft and save the draft * Convert a send mode to a draft and save the draft
*/ */
private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) { private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) = withState {
withState { when {
when (it.sendMode) { it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> {
is SendMode.REGULAR -> room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback()) setState { copy(sendMode = it.sendMode.copy(action.draft)) }
is SendMode.REPLY -> room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback()) room.saveDraft(UserDraft.REGULAR(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()) it.sendMode is SendMode.REPLY -> {
}.exhaustive 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() { private fun getDraftIfAny() {
room.rx().liveDrafts() val currentDraft = room.getDraft() ?: return
.subscribe {
Timber.d("Draft update --> SetState")
setState { setState {
val draft = it.lastOrNull() ?: UserDraft.REGULAR("")
copy( copy(
// Create a sendMode from a draft and retrieve the TimelineEvent // Create a sendMode from a draft and retrieve the TimelineEvent
sendMode = when (draft) { sendMode = when (currentDraft) {
is UserDraft.REGULAR -> SendMode.REGULAR(draft.text) is UserDraft.REGULAR -> SendMode.REGULAR(currentDraft.text, false)
is UserDraft.QUOTE -> { is UserDraft.QUOTE -> {
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
SendMode.QUOTE(timelineEvent, draft.text) SendMode.QUOTE(timelineEvent, currentDraft.text)
} }
} }
is UserDraft.REPLY -> { is UserDraft.REPLY -> {
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
SendMode.REPLY(timelineEvent, draft.text) SendMode.REPLY(timelineEvent, currentDraft.text)
} }
} }
is UserDraft.EDIT -> { is UserDraft.EDIT -> {
room.getTimeLineEvent(draft.linkedEventId)?.let { timelineEvent -> room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
SendMode.EDIT(timelineEvent, draft.text) SendMode.EDIT(timelineEvent, currentDraft.text)
} }
} }
} ?: SendMode.REGULAR("") } ?: SendMode.REGULAR("", fromSharing = false)
) )
} }
} }
.disposeOnClear()
}
private fun handleUserIsTyping(action: RoomDetailAction.UserIsTyping) { private fun handleUserIsTyping(action: RoomDetailAction.UserIsTyping) {
if (vectorPreferences.sendTypingNotifs()) { if (vectorPreferences.sendTypingNotifs()) {
@ -533,6 +540,8 @@ class RoomDetailViewModel @AssistedInject constructor(
// For now always disable when not in developer mode, worker cancellation is not working properly // For now always disable when not in developer mode, worker cancellation is not working properly
timeline.pendingEventCount() > 0 && vectorPreferences.developerMode() timeline.pendingEventCount() > 0 && vectorPreferences.developerMode()
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true 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.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
R.id.open_matrix_apps -> true R.id.open_matrix_apps -> true
R.id.voice_call, R.id.voice_call,
@ -740,9 +749,16 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
} }
private fun popDraft() { 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()) room.deleteDraft(NoOpMatrixCallback())
} }
}
private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) { private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) {
session.joinRoom(command.roomAlias, command.reason, emptyList(), object : MatrixCallback<Unit> { session.joinRoom(command.roomAlias, command.reason, emptyList(), object : MatrixCallback<Unit> {
@ -915,74 +931,25 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
private fun handleEditAction(action: RoomDetailAction.EnterEditMode) { private fun handleEditAction(action: RoomDetailAction.EnterEditMode) {
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.EDIT(timelineEvent, action.text)) } setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent() ?: "")) }
timelineEvent.root.eventId?.let {
room.saveDraft(UserDraft.EDIT(it, timelineEvent.getTextEditableContent() ?: ""), NoOpMatrixCallback())
}
} }
} }
private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) { private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) {
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) } 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) { private fun handleReplyAction(action: RoomDetailAction.EnterReplyMode) {
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) } 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 { private fun handleEnterRegularMode(action: RoomDetailAction.EnterRegularMode) = setState {
if (it.sendMode is SendMode.EDIT) { copy(sendMode = SendMode.REGULAR(action.text, action.fromSharing))
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 handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) { private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) {

View File

@ -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...) * Depending on the state the bottom toolbar will change (icons/preview/actions...)
*/ */
sealed class SendMode(open val text: String) { 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 QUOTE(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
data class EDIT(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) data class REPLY(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text)
@ -58,7 +63,7 @@ data class RoomDetailViewState(
val asyncRoomSummary: Async<RoomSummary> = Uninitialized, val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val activeRoomWidgets: Async<List<Widget>> = Uninitialized, val activeRoomWidgets: Async<List<Widget>> = Uninitialized,
val typingMessage: String? = null, val typingMessage: String? = null,
val sendMode: SendMode = SendMode.REGULAR(""), val sendMode: SendMode = SendMode.REGULAR("", false),
val tombstoneEvent: Event? = null, val tombstoneEvent: Event? = null,
val tombstoneEventHandling: Async<String> = Uninitialized, val tombstoneEventHandling: Async<String> = Uninitialized,
val syncState: SyncState = SyncState.Idle, val syncState: SyncState = SyncState.Idle,
@ -67,6 +72,7 @@ data class RoomDetailViewState(
val canShowJumpToReadMarker: Boolean = true, val canShowJumpToReadMarker: Boolean = true,
val changeMembershipState: ChangeMembershipState = ChangeMembershipState.Unknown, val changeMembershipState: ChangeMembershipState = ChangeMembershipState.Unknown,
val canSendMessage: Boolean = true, val canSendMessage: Boolean = true,
val canInvite: Boolean = true,
val isAllowedToManageWidgets: Boolean = false, val isAllowedToManageWidgets: Boolean = false,
val isAllowedToStartWebRTCCall: Boolean = true val isAllowedToStartWebRTCCall: Boolean = true
) : MvRxState { ) : MvRxState {

View File

@ -25,12 +25,15 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_SWIPE import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_SWIPE
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.EpoxyTouchHelperCallback import com.airbnb.epoxy.EpoxyTouchHelperCallback
import com.airbnb.epoxy.EpoxyViewHolder import com.airbnb.epoxy.EpoxyViewHolder
import im.vector.app.R
import im.vector.app.features.themes.ThemeUtils
import timber.log.Timber import timber.log.Timber
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.min import kotlin.math.min
@ -52,7 +55,16 @@ class RoomMessageTouchHelperCallback(private val context: Context,
private var replyButtonProgress: Float = 0F private var replyButtonProgress: Float = 0F
private var lastReplyButtonAnimationTime: Long = 0 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 triggerDistance = convertToPx(100)
private val minShowDistance = convertToPx(20) private val minShowDistance = convertToPx(20)

View File

@ -259,8 +259,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
findPreference<VectorPreference>("SETTINGS_SIGN_OUT_KEY")!! findPreference<VectorPreference>("SETTINGS_SIGN_OUT_KEY")!!
.onPreferenceClickListener = Preference.OnPreferenceClickListener { .onPreferenceClickListener = Preference.OnPreferenceClickListener {
activity?.let { activity?.let {
SignOutUiWorker(requireActivity()) SignOutUiWorker(requireActivity()).perform()
.perform(requireContext())
} }
false false

View File

@ -31,7 +31,7 @@ import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
class SignoutBottomSheetActionButton @JvmOverloads constructor( class SignOutBottomSheetActionButton @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) { ) : LinearLayout(context, attrs, defStyleAttr) {
@ -80,11 +80,11 @@ class SignoutBottomSheetActionButton @JvmOverloads constructor(
inflate(context, R.layout.item_signout_action, this) inflate(context, R.layout.item_signout_action, this)
ButterKnife.bind(this) ButterKnife.bind(this)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.SignoutBottomSheetActionButton, 0, 0) val typedArray = context.obtainStyledAttributes(attrs, R.styleable.SignOutBottomSheetActionButton, 0, 0)
title = typedArray.getString(R.styleable.SignoutBottomSheetActionButton_actionTitle) ?: "" title = typedArray.getString(R.styleable.SignOutBottomSheetActionButton_actionTitle) ?: ""
leftIcon = typedArray.getDrawable(R.styleable.SignoutBottomSheetActionButton_leftIcon) leftIcon = typedArray.getDrawable(R.styleable.SignOutBottomSheetActionButton_leftIcon)
tint = typedArray.getColor(R.styleable.SignoutBottomSheetActionButton_iconTint, ThemeUtils.getColor(context, android.R.attr.textColor)) 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)) textColor = typedArray.getColor(R.styleable.SignOutBottomSheetActionButton_textColor, ThemeUtils.getColor(context, android.R.attr.textColor))
typedArray.recycle() typedArray.recycle()

View File

@ -35,8 +35,6 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog 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.R
import im.vector.app.core.di.ScreenComponent import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.dialogs.ExportKeysDialog 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.keysbackup.setup.KeysBackupSetupActivity
import im.vector.app.features.crypto.recover.BootstrapBottomSheet import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.crypto.recover.SetupMode 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 timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -57,21 +58,6 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(),
@BindView(R.id.bottom_sheet_signout_backingup_status_group) @BindView(R.id.bottom_sheet_signout_backingup_status_group)
lateinit var backingUpStatusGroup: ViewGroup 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) @BindView(R.id.bottom_sheet_signout_icon_progress_bar)
lateinit var backupProgress: ProgressBar lateinit var backupProgress: ProgressBar
@ -138,6 +124,10 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(),
} }
} }
signOutButton.action = {
onSignOut?.run()
}
exportManuallyButton.action = { exportManuallyButton.action = {
withState(viewModel) { state -> withState(viewModel) { state ->
queryExportKeys(state.userId, QUERY_EXPORT_KEYS) queryExportKeys(state.userId, QUERY_EXPORT_KEYS)
@ -182,10 +172,10 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(),
// we should show option to setup 4S // we should show option to setup 4S
setupRecoveryButton.isVisible = true setupRecoveryButton.isVisible = true
setupMegolmBackupButton.isVisible = false setupMegolmBackupButton.isVisible = false
signOutButton.isVisible = false
// We let the option to ignore and quit // We let the option to ignore and quit
exportManuallyButton.isVisible = true exportManuallyButton.isVisible = true
exitAnywayButton.isVisible = true exitAnywayButton.isVisible = true
signOutButton.isVisible = false
} else if (state.keysBackupState == KeysBackupState.Unknown || state.keysBackupState == KeysBackupState.Disabled) { } else if (state.keysBackupState == KeysBackupState.Unknown || state.keysBackupState == KeysBackupState.Disabled) {
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_no_backup) sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_no_backup)
backingUpStatusGroup.isVisible = false backingUpStatusGroup.isVisible = false
@ -194,10 +184,10 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(),
// we should show option to setup 4S // we should show option to setup 4S
setupRecoveryButton.isVisible = false setupRecoveryButton.isVisible = false
setupMegolmBackupButton.isVisible = true setupMegolmBackupButton.isVisible = true
signOutButton.isVisible = false
// We let the option to ignore and quit // We let the option to ignore and quit
exportManuallyButton.isVisible = true exportManuallyButton.isVisible = true
exitAnywayButton.isVisible = true exitAnywayButton.isVisible = true
signOutButton.isVisible = false
} else { } else {
// so keybackup is setup // so keybackup is setup
// You should wait until all are uploaded // You should wait until all are uploaded
@ -213,11 +203,12 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(),
backupCompleteImage.isVisible = true backupCompleteImage.isVisible = true
backupStatusTex.text = getString(R.string.keys_backup_info_keys_all_backup_up) 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 // You can signout
signOutButton.isVisible = true signOutButton.isVisible = true
} }
KeysBackupState.WillBackUp, KeysBackupState.WillBackUp,
KeysBackupState.BackingUp -> { KeysBackupState.BackingUp -> {
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backing_up) sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backing_up)
@ -228,18 +219,21 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(),
backupCompleteImage.isVisible = false backupCompleteImage.isVisible = false
backupStatusTex.text = getString(R.string.sign_out_bottom_sheet_backing_up_keys) 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 exitAnywayButton.isVisible = true
signOutButton.isVisible = false
} }
KeysBackupState.NotTrusted -> { KeysBackupState.NotTrusted -> {
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backup_not_active) sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backup_not_active)
// It's not trusted and we know there are unsaved keys.. // It's not trusted and we know there are unsaved keys..
backingUpStatusGroup.isVisible = false backingUpStatusGroup.isVisible = false
exportManuallyButton.isVisible = true
// option to enter pass/key // option to enter pass/key
setupMegolmBackupButton.isVisible = true setupMegolmBackupButton.isVisible = true
exportManuallyButton.isVisible = true
exitAnywayButton.isVisible = true exitAnywayButton.isVisible = true
signOutButton.isVisible = false
} }
else -> { else -> {
// mmm.. strange state // mmm.. strange state
@ -253,21 +247,23 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(),
when (state.hasBeenExportedToFile) { when (state.hasBeenExportedToFile) {
is Loading -> { is Loading -> {
signoutExportingLoading.isVisible = true signoutExportingLoading.isVisible = true
hideViews(setupRecoveryButton, backingUpStatusGroup.isVisible = false
setupMegolmBackupButton,
exportManuallyButton, setupRecoveryButton.isVisible = false
backingUpStatusGroup, setupMegolmBackupButton.isVisible = false
signOutButton) exportManuallyButton.isVisible = false
exitAnywayButton.isVisible = true exitAnywayButton.isVisible = true
signOutButton.isVisible = false
} }
is Success -> { is Success -> {
if (state.hasBeenExportedToFile.invoke()) { if (state.hasBeenExportedToFile.invoke()) {
sheetTitle.text = getString(R.string.action_sign_out_confirmation_simple) sheetTitle.text = getString(R.string.action_sign_out_confirmation_simple)
hideViews(setupRecoveryButton, backingUpStatusGroup.isVisible = false
setupMegolmBackupButton,
exportManuallyButton, setupRecoveryButton.isVisible = false
backingUpStatusGroup, setupMegolmBackupButton.isVisible = false
exitAnywayButton) exportManuallyButton.isVisible = false
exitAnywayButton.isVisible = false
signOutButton.isVisible = true signOutButton.isVisible = true
} }
} }
@ -315,8 +311,4 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(),
} }
} }
} }
private fun hideViews(vararg views: View) {
views.forEach { it.isVisible = false }
}
} }

View File

@ -16,11 +16,9 @@
package im.vector.app.features.workers.signout package im.vector.app.features.workers.signout
import android.content.Context
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import im.vector.app.R 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.cannotLogoutSafely
import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.extensions.vectorComponent
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
@ -28,11 +26,8 @@ import im.vector.app.features.MainActivityArgs
class SignOutUiWorker(private val activity: FragmentActivity) { class SignOutUiWorker(private val activity: FragmentActivity) {
lateinit var activeSessionHolder: ActiveSessionHolder fun perform() {
val session = activity.vectorComponent().activeSessionHolder().getSafeActiveSession() ?: return
fun perform(context: Context) {
activeSessionHolder = context.vectorComponent().activeSessionHolder()
val session = activeSessionHolder.getActiveSession()
if (session.cannotLogoutSafely()) { 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 // 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() val signOutDialog = SignOutBottomSheetDialogFragment.newInstance()

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:pathData="M14.89,6.83C15.05,7.25 15.46,7.53 15.91,7.53C16.51,7.53 17,8.02 17,8.62V9.38C17,9.98 16.51,10.47 15.91,10.47C15.46,10.47 15.05,10.75 14.89,11.17C14.8768,11.2031 14.8635,11.2366 14.8502,11.2704C14.8031,11.3901 14.7546,11.5131 14.7,11.63C14.51,12.04 14.6,12.52 14.92,12.84C15.35,13.26 15.35,13.95 14.92,14.38L14.38,14.92C13.96,15.35 13.27,15.35 12.84,14.92C12.53,14.6 12.04,14.51 11.63,14.7C11.48,14.77 11.33,14.83 11.17,14.89C10.75,15.05 10.47,15.46 10.47,15.91C10.47,16.51 9.98,17 9.38,17H8.62C8.02,17 7.53,16.51 7.53,15.91C7.53,15.46 7.25,15.05 6.83,14.89C6.7969,14.8768 6.7634,14.8635 6.7296,14.8502C6.61,14.8031 6.4869,14.7546 6.37,14.7C5.96,14.51 5.48,14.6 5.16,14.92C4.74,15.35 4.05,15.35 3.62,14.92L3.08,14.38C2.65,13.96 2.65,13.27 3.08,12.84C3.4,12.53 3.49,12.04 3.3,11.63C3.23,11.48 3.17,11.33 3.11,11.17C2.95,10.75 2.54,10.47 2.09,10.47C1.49,10.47 1,9.98 1,9.38V8.62C1,8.02 1.49,7.53 2.09,7.53C2.54,7.53 2.95,7.25 3.11,6.83C3.1416,6.729 3.1811,6.632 3.221,6.534C3.2444,6.4767 3.2679,6.419 3.29,6.36C3.48,5.95 3.39,5.47 3.07,5.15C2.64,4.73 2.64,4.04 3.07,3.61L3.62,3.08C4.04,2.65 4.73,2.65 5.16,3.08C5.47,3.4 5.96,3.49 6.37,3.3C6.52,3.23 6.67,3.16 6.83,3.11C7.25,2.95 7.53,2.54 7.53,2.09C7.53,1.49 8.02,1 8.62,1H9.38C9.98,1 10.47,1.49 10.47,2.09C10.47,2.55 10.75,2.95 11.17,3.11C11.2031,3.1232 11.2366,3.1365 11.2704,3.1498C11.3901,3.1969 11.5131,3.2454 11.63,3.3C12.04,3.49 12.52,3.4 12.84,3.08C13.26,2.65 13.95,2.65 14.38,3.08L14.92,3.62C15.35,4.04 15.35,4.73 14.92,5.16C14.6,5.47 14.51,5.96 14.7,6.37C14.77,6.52 14.83,6.67 14.89,6.83ZM9,13C6.79,13 5,11.21 5,9C5,6.79 6.79,5 9,5C11.21,5 13,6.79 13,9C13,11.21 11.21,13 9,13Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:pathData="M9,2L14.8586,1.1631C15.461,1.077 16,1.5445 16,2.153V15.847C16,16.4555 15.461,16.923 14.8586,16.8369L9,16V2Z"
android:fillColor="#000000"/>
<group>
<clip-path
android:pathData="M3,2L10,2A1,1 0,0 1,11 3L11,15A1,1 0,0 1,10 16L3,16A1,1 0,0 1,2 15L2,3A1,1 0,0 1,3 2z"/>
<path
android:pathData="M3,2L10,2A1,1 0,0 1,11 3L11,15A1,1 0,0 1,10 16L3,16A1,1 0,0 1,2 15L2,3A1,1 0,0 1,3 2z"
android:strokeWidth="3"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
</group>
</vector>

View File

@ -81,7 +81,7 @@
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
<im.vector.app.features.workers.signout.SignoutBottomSheetActionButton <im.vector.app.features.workers.signout.SignOutBottomSheetActionButton
android:id="@+id/setupRecoveryButton" android:id="@+id/setupRecoveryButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -91,7 +91,7 @@
app:textColor="?riotx_text_secondary" /> app:textColor="?riotx_text_secondary" />
<im.vector.app.features.workers.signout.SignoutBottomSheetActionButton <im.vector.app.features.workers.signout.SignOutBottomSheetActionButton
android:id="@+id/setupMegolmBackupButton" android:id="@+id/setupMegolmBackupButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -100,7 +100,7 @@
app:leftIcon="@drawable/backup_keys" app:leftIcon="@drawable/backup_keys"
app:textColor="?riotx_text_secondary" /> app:textColor="?riotx_text_secondary" />
<im.vector.app.features.workers.signout.SignoutBottomSheetActionButton <im.vector.app.features.workers.signout.SignOutBottomSheetActionButton
android:id="@+id/exportManuallyButton" android:id="@+id/exportManuallyButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -109,7 +109,7 @@
app:leftIcon="@drawable/ic_download" app:leftIcon="@drawable/ic_download"
app:textColor="?riotx_text_secondary" /> app:textColor="?riotx_text_secondary" />
<im.vector.app.features.workers.signout.SignoutBottomSheetActionButton <im.vector.app.features.workers.signout.SignOutBottomSheetActionButton
android:id="@+id/exitAnywayButton" android:id="@+id/exitAnywayButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -118,7 +118,7 @@
app:leftIcon="@drawable/ic_material_leave" app:leftIcon="@drawable/ic_material_leave"
app:textColor="@color/riotx_destructive_accent" /> app:textColor="@color/riotx_destructive_accent" />
<im.vector.app.features.workers.signout.SignoutBottomSheetActionButton <im.vector.app.features.workers.signout.SignOutBottomSheetActionButton
android:id="@+id/signOutButton" android:id="@+id/signOutButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?riotx_background"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
@ -42,11 +43,12 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:maxLines="1" android:maxLines="1"
android:singleLine="true" android:singleLine="true"
android:textColor="?riotx_text_primary" android:textColor="?riotx_text_primary"
android:textSize="15sp" android:textSize="15sp"
app:layout_constraintEnd_toStartOf="@id/homeDrawerHeaderSettingsView" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView" app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView"
app:layout_constraintTop_toBottomOf="@+id/homeDrawerHeaderAvatarView" app:layout_constraintTop_toBottomOf="@+id/homeDrawerHeaderAvatarView"
tools:text="@sample/matrix.json/data/displayName" /> tools:text="@sample/matrix.json/data/displayName" />
@ -55,39 +57,71 @@
android:id="@+id/homeDrawerUserIdView" android:id="@+id/homeDrawerUserIdView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginBottom="17dp" android:layout_marginBottom="17dp"
android:maxLines="1" android:maxLines="1"
android:singleLine="true" android:singleLine="true"
android:textColor="?riotx_text_secondary" android:textColor="?riotx_text_secondary"
android:textSize="15sp" android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/homeDrawerHeaderSettingsView" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView" app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView"
app:layout_constraintTop_toBottomOf="@+id/homeDrawerUsernameView" app:layout_constraintTop_toBottomOf="@+id/homeDrawerUsernameView"
tools:text="@sample/matrix.json/data/mxid" /> tools:text="@sample/matrix.json/data/mxid" />
<ImageView
android:id="@+id/homeDrawerHeaderSettingsView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/room_sliding_menu_settings"
android:padding="16dp"
android:src="@drawable/ic_settings_x"
android:tint="?riotx_text_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/homeDrawerGroupListContainer" android:id="@+id/homeDrawerGroupListContainer"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="?riotx_background"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@+id/homeDrawerBottomSeparator"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/homeDrawerHeader" /> app:layout_constraintTop_toBottomOf="@+id/homeDrawerHeader" />
<View
android:id="@+id/homeDrawerBottomSeparator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/vctr_list_divider_color"
app:layout_constraintBottom_toTopOf="@+id/homeDrawerHeaderSettingsView" />
<TextView
android:id="@+id/homeDrawerHeaderSettingsView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawablePadding="9dp"
android:gravity="center_vertical"
android:minWidth="120dp"
android:minHeight="52dp"
android:padding="16dp"
android:text="@string/settings"
android:textSize="14sp"
android:tint="?riotx_android_secondary"
app:drawableStartCompat="@drawable/ic_settings_18dp"
app:drawableTint="?riotx_android_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/homeDrawerHeaderSignoutView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawablePadding="9dp"
android:gravity="center_vertical"
android:minWidth="120dp"
android:minHeight="52dp"
android:padding="16dp"
android:text="@string/logout"
android:textSize="14sp"
android:tint="?riotx_android_secondary"
app:drawableStartCompat="@drawable/ic_signout_18dp"
app:drawableTint="?riotx_android_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -105,6 +105,7 @@
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:src="@drawable/ic_edit" android:src="@drawable/ic_edit"
android:visibility="gone" android:visibility="gone"
android:tint="?riotx_text_primary"
app:layout_constraintBottom_toBottomOf="@+id/roomNameView" app:layout_constraintBottom_toBottomOf="@+id/roomNameView"
app:layout_constraintEnd_toStartOf="@+id/roomUnreadCounterBadgeView" app:layout_constraintEnd_toStartOf="@+id/roomUnreadCounterBadgeView"
app:layout_constraintStart_toEndOf="@+id/roomNameView" app:layout_constraintStart_toEndOf="@+id/roomNameView"

View File

@ -2,6 +2,12 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_home_setting"
android:icon="@drawable/ic_settings_x"
android:title="@string/settings"
app:showAsAction="never" />
<item <item
android:id="@+id/menu_home_suggestion" android:id="@+id/menu_home_suggestion"
android:icon="@drawable/ic_material_bug_report" android:icon="@drawable/ic_material_bug_report"
@ -15,8 +21,8 @@
<item <item
android:id="@+id/menu_home_filter" android:id="@+id/menu_home_filter"
android:icon="@drawable/ic_search" android:icon="@drawable/ic_search"
app:iconTint="?riotx_text_secondary"
android:title="@string/home_filter_placeholder_home" android:title="@string/home_filter_placeholder_home"
app:iconTint="?riotx_text_secondary"
app:showAsAction="always" /> app:showAsAction="always" />
</menu> </menu>

View File

@ -3,11 +3,22 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/timeline_setting"
android:icon="@drawable/ic_settings_x"
android:title="@string/settings"
app:showAsAction="never" />
<item <item
android:id="@+id/search" android:id="@+id/search"
android:title="@string/search" android:title="@string/search"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/invite"
android:title="@string/invite"
app:showAsAction="never" />
<item <item
android:id="@+id/video_call" android:id="@+id/video_call"
android:icon="@drawable/ic_video" android:icon="@drawable/ic_video"

View File

@ -60,7 +60,7 @@
<attr name="forceStartPadding" format="boolean" /> <attr name="forceStartPadding" format="boolean" />
</declare-styleable> </declare-styleable>
<declare-styleable name="SignoutBottomSheetActionButton"> <declare-styleable name="SignOutBottomSheetActionButton">
<attr name="iconTint" format="color" /> <attr name="iconTint" format="color" />
<attr name="actionTitle"/> <attr name="actionTitle"/>
<attr name="leftIcon" /> <attr name="leftIcon" />