This commit is contained in:
Benoit Marty 2019-04-03 16:36:45 +02:00
parent 3091a337c9
commit 08dacacdda
20 changed files with 883 additions and 19 deletions

View file

@ -24,7 +24,7 @@ interface MatrixCallback<in T> {
/** /**
* On success method, default to no-op * On success method, default to no-op
* @param data the data successfuly returned from the async function * @param data the data successfully returned from the async function
*/ */
fun onSuccess(data: T) { fun onSuccess(data: T) {
//no-op //no-op

View file

@ -22,13 +22,19 @@ import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.UserService
/** /**
* This interface defines interactions with a session. * This interface defines interactions with a session.
* An instance of a session will be provided by the SDK. * An instance of a session will be provided by the SDK.
*/ */
interface Session : RoomService, GroupService, UserService, CryptoService { interface Session :
RoomService,
GroupService,
UserService,
CryptoService,
SignOutService {
/** /**
* The params associated to the session * The params associated to the session

View file

@ -26,16 +26,16 @@ interface ReadService {
/** /**
* Force the read marker to be set on the latest event. * Force the read marker to be set on the latest event.
*/ */
fun markAllAsRead(callback: MatrixCallback<Void>) fun markAllAsRead(callback: MatrixCallback<Unit>)
/** /**
* Set the read receipt on the event with provided eventId. * Set the read receipt on the event with provided eventId.
*/ */
fun setReadReceipt(eventId: String, callback: MatrixCallback<Void>) fun setReadReceipt(eventId: String, callback: MatrixCallback<Unit>)
/** /**
* Set the read marker on the event with provided eventId. * Set the read marker on the event with provided eventId.
*/ */
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Void>) fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
} }

View file

@ -0,0 +1,31 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.signout
import im.vector.matrix.android.api.MatrixCallback
/**
* This interface defines a method to sign out. It's implemented at the session level.
*/
interface SignOutService {
/**
* Sign out
*/
fun signOut(callback: MatrixCallback<Unit>)
}

View file

@ -25,4 +25,5 @@ internal interface SessionParamsStore {
fun save(sessionParams: SessionParams): Try<SessionParams> fun save(sessionParams: SessionParams): Try<SessionParams>
fun delete()
} }

View file

@ -17,13 +17,13 @@
package im.vector.matrix.android.internal.auth.db package im.vector.matrix.android.internal.auth.db
import arrow.core.Try import arrow.core.Try
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.auth.SessionParamsStore
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
internal class RealmSessionParamsStore(private val mapper: SessionParamsMapper, internal class RealmSessionParamsStore(private val mapper: SessionParamsMapper,
private val realmConfiguration: RealmConfiguration) : SessionParamsStore { private val realmConfiguration: RealmConfiguration) : SessionParamsStore {
override fun save(sessionParams: SessionParams): Try<SessionParams> { override fun save(sessionParams: SessionParams): Try<SessionParams> {
return Try { return Try {
@ -50,4 +50,14 @@ internal class RealmSessionParamsStore(private val mapper: SessionParamsMapper,
return sessionParams return sessionParams
} }
override fun delete() {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.where(SessionParamsEntity::class.java)
.findAll()
.deleteAllFromRealm()
}
realm.close()
}
} }

View file

@ -20,6 +20,7 @@ import android.os.Looper
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.content.ContentUrlResolver
@ -29,13 +30,16 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.di.MatrixKoinComponent import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.di.MatrixKoinHolder import im.vector.matrix.android.internal.di.MatrixKoinHolder
import im.vector.matrix.android.internal.session.group.GroupModule import im.vector.matrix.android.internal.session.group.GroupModule
import im.vector.matrix.android.internal.session.room.RoomModule import im.vector.matrix.android.internal.session.room.RoomModule
import im.vector.matrix.android.internal.session.signout.SignOutModule
import im.vector.matrix.android.internal.session.sync.SyncModule import im.vector.matrix.android.internal.session.sync.SyncModule
import im.vector.matrix.android.internal.session.sync.job.SyncThread import im.vector.matrix.android.internal.session.sync.job.SyncThread
import im.vector.matrix.android.internal.session.user.UserModule import im.vector.matrix.android.internal.session.user.UserModule
@ -57,6 +61,7 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
private val roomService by inject<RoomService>() private val roomService by inject<RoomService>()
private val groupService by inject<GroupService>() private val groupService by inject<GroupService>()
private val userService by inject<UserService>() private val userService by inject<UserService>()
private val signOutService by inject<SignOutService>()
private val syncThread by inject<SyncThread>() private val syncThread by inject<SyncThread>()
private val contentUrlResolver by inject<ContentUrlResolver>() private val contentUrlResolver by inject<ContentUrlResolver>()
private var isOpen = false private var isOpen = false
@ -70,8 +75,9 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
val syncModule = SyncModule().definition val syncModule = SyncModule().definition
val roomModule = RoomModule().definition val roomModule = RoomModule().definition
val groupModule = GroupModule().definition val groupModule = GroupModule().definition
val signOutModule = SignOutModule().definition
val userModule = UserModule().definition val userModule = UserModule().definition
MatrixKoinHolder.instance.loadModules(listOf(sessionModule, syncModule, roomModule, groupModule, userModule)) MatrixKoinHolder.instance.loadModules(listOf(sessionModule, syncModule, roomModule, groupModule, signOutModule, userModule))
scope = getKoin().getOrCreateScope(SCOPE) scope = getKoin().getOrCreateScope(SCOPE)
if (!monarchy.isMonarchyThreadOpen) { if (!monarchy.isMonarchyThreadOpen) {
monarchy.openManually() monarchy.openManually()
@ -94,6 +100,23 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
isOpen = false isOpen = false
} }
@MainThread
override fun signOut(callback: MatrixCallback<Unit>) {
assert(isOpen)
return signOutService.signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// Close the session
close()
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
})
}
override fun contentUrlResolver(): ContentUrlResolver { override fun contentUrlResolver(): ContentUrlResolver {
return contentUrlResolver return contentUrlResolver
} }

View file

@ -22,6 +22,7 @@ import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.session.content.DefaultContentUrlResolver import im.vector.matrix.android.internal.session.content.DefaultContentUrlResolver
@ -33,6 +34,7 @@ import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomMemberDisplayNameResolver
import im.vector.matrix.android.internal.session.room.prune.EventsPruner import im.vector.matrix.android.internal.session.room.prune.EventsPruner
import im.vector.matrix.android.internal.session.signout.DefaultSignOutService
import im.vector.matrix.android.internal.session.user.DefaultUserService import im.vector.matrix.android.internal.session.user.DefaultUserService
import im.vector.matrix.android.internal.session.user.UserEntityUpdater import im.vector.matrix.android.internal.session.user.UserEntityUpdater
import im.vector.matrix.android.internal.util.md5 import im.vector.matrix.android.internal.util.md5
@ -102,6 +104,10 @@ internal class SessionModule(private val sessionParams: SessionParams) {
DefaultGroupService(get()) as GroupService DefaultGroupService(get()) as GroupService
} }
scope(DefaultSession.SCOPE) {
DefaultSignOutService(get(), get()) as SignOutService
}
scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
DefaultUserService(get()) as UserService DefaultUserService(get()) as UserService
} }

View file

@ -30,20 +30,20 @@ internal class DefaultReadService(private val roomId: String,
private val setReadMarkersTask: SetReadMarkersTask, private val setReadMarkersTask: SetReadMarkersTask,
private val taskExecutor: TaskExecutor) : ReadService { private val taskExecutor: TaskExecutor) : ReadService {
override fun markAllAsRead(callback: MatrixCallback<Void>) { override fun markAllAsRead(callback: MatrixCallback<Unit>) {
val latestEvent = getLatestEvent() val latestEvent = getLatestEvent()
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = latestEvent?.eventId, readReceiptEventId = latestEvent?.eventId) val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = latestEvent?.eventId, readReceiptEventId = latestEvent?.eventId)
setReadMarkersTask.configureWith(params).executeBy(taskExecutor) setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
} }
override fun setReadReceipt(eventId: String, callback: MatrixCallback<Void>) { override fun setReadReceipt(eventId: String, callback: MatrixCallback<Unit>) {
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId) val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId)
setReadMarkersTask.configureWith(params).executeBy(taskExecutor) setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
} }
override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Void>) { override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>) {
val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = null) val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = null)
setReadMarkersTask.configureWith(params).executeBy(taskExecutor) setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
} }
private fun getLatestEvent(): EventEntity? { private fun getLatestEvent(): EventEntity? {

View file

@ -0,0 +1,34 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.signout
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
internal class DefaultSignOutService(private val signOutTask: SignOutTask,
private val taskExecutor: TaskExecutor) : SignOutService {
override fun signOut(callback: MatrixCallback<Unit>) {
signOutTask
.configureWith(Unit)
.dispatchTo(callback)
.executeBy(taskExecutor)
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.signout
import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.POST
internal interface SignOutAPI {
/**
* Invalidate the access token, so that it can no longer be used for authorization.
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "logout")
fun signOut(): Call<Unit>
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.signout
import im.vector.matrix.android.internal.session.DefaultSession
import org.koin.dsl.module.module
import retrofit2.Retrofit
class SignOutModule {
val definition = module(override = true) {
scope(DefaultSession.SCOPE) {
val retrofit: Retrofit = get()
retrofit.create(SignOutAPI::class.java)
}
scope(DefaultSession.SCOPE) {
DefaultSignOutTask(get(), get()) as SignOutTask
}
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.signout
import arrow.core.Try
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
internal interface SignOutTask : Task<Unit, Unit>
internal class DefaultSignOutTask(private val signOutAPI: SignOutAPI,
private val sessionParamsStore: SessionParamsStore) : SignOutTask {
override fun execute(params: Unit): Try<Unit> {
return executeRequest<Unit> {
apiCall = signOutAPI.signOut()
}.map {
// TODO Clear DB, media cache, etc.
sessionParamsStore.delete()
}
}
}

View file

@ -27,6 +27,7 @@ import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.hideKeyboard import im.vector.riotredesign.core.extensions.hideKeyboard
import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.extensions.observeEvent
@ -38,6 +39,7 @@ import im.vector.riotredesign.features.home.room.detail.LoadingRoomDetailFragmen
import im.vector.riotredesign.features.rageshake.BugReporter import im.vector.riotredesign.features.rageshake.BugReporter
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotredesign.features.settings.VectorSettingsActivity import im.vector.riotredesign.features.settings.VectorSettingsActivity
import im.vector.riotredesign.features.workers.signout.SignOutUiWorker
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope import org.koin.android.scope.ext.android.bindScope
@ -114,6 +116,10 @@ class HomeActivity : RiotActivity(), ToolbarConfigurable {
startActivity(VectorSettingsActivity.getIntent(this, "TODO")) startActivity(VectorSettingsActivity.getIntent(this, "TODO"))
return true return true
} }
R.id.sliding_menu_sign_out -> {
SignOutUiWorker(this).perform(Matrix.getInstance().currentSession!!)
return true
}
} }
return true return true

View file

@ -97,7 +97,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
.subscribeBy(onNext = { actions -> .subscribeBy(onNext = { actions ->
val mostRecentEvent = actions.maxBy { it.event.displayIndex } val mostRecentEvent = actions.maxBy { it.event.displayIndex }
mostRecentEvent?.event?.root?.eventId?.let { eventId -> mostRecentEvent?.event?.root?.eventId?.let { eventId ->
room.setReadReceipt(eventId, callback = object : MatrixCallback<Void> {}) room.setReadReceipt(eventId, callback = object : MatrixCallback<Unit> {})
} }
}) })
.disposeOnClear() .disposeOnClear()

View file

@ -0,0 +1,257 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.features.workers.signout
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import butterknife.BindView
import butterknife.ButterKnife
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R
import org.koin.android.ext.android.inject
class SignOutBottomSheetDialogFragment : BottomSheetDialogFragment() {
val session by inject<Session>()
@BindView(R.id.bottom_sheet_signout_warning_text)
lateinit var sheetTitle: TextView
@BindView(R.id.bottom_sheet_signout_backingup_status_group)
lateinit var backingUpStatusGroup: ViewGroup
@BindView(R.id.keys_backup_setup)
lateinit var setupClickableView: View
@BindView(R.id.keys_backup_activate)
lateinit var activateClickableView: View
@BindView(R.id.keys_backup_dont_want)
lateinit var dontWantClickableView: View
@BindView(R.id.bottom_sheet_signout_icon_progress_bar)
lateinit var backupProgress: ProgressBar
@BindView(R.id.bottom_sheet_signout_icon)
lateinit var backupCompleteImage: ImageView
@BindView(R.id.bottom_sheet_backup_status_text)
lateinit var backupStatusTex: TextView
@BindView(R.id.bottom_sheet_signout_button)
lateinit var signoutClickableView: View
@BindView(R.id.root_layout)
lateinit var rootLayout: ViewGroup
var onSignOut: Runnable? = null
companion object {
fun newInstance(userId: String) = SignOutBottomSheetDialogFragment()
private const val EXPORT_REQ = 0
}
init {
isCancelable = true
}
private lateinit var viewModel: SignOutViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(SignOutViewModel::class.java)
viewModel.init(session)
setupClickableView.setOnClickListener {
context?.let { context ->
// TODO startActivityForResult(KeysBackupSetupActivity.intent(context, getExtraMatrixID(), true), EXPORT_REQ)
}
}
activateClickableView.setOnClickListener {
context?.let { context ->
// TODO startActivity(KeysBackupManageActivity.intent(context, getExtraMatrixID()))
}
}
signoutClickableView.setOnClickListener {
this.onSignOut?.run()
}
dontWantClickableView.setOnClickListener { _ ->
context?.let {
AlertDialog.Builder(it)
.setTitle(R.string.are_you_sure)
.setMessage(R.string.sign_out_bottom_sheet_will_lose_secure_messages)
.setPositiveButton(R.string.backup) { _, _ ->
/* TODO
when (viewModel.keysBackupState.value) {
KeysBackupStateManager.KeysBackupState.NotTrusted -> {
context?.let { context ->
startActivity(KeysBackupManageActivity.intent(context, getExtraMatrixID()))
}
}
KeysBackupStateManager.KeysBackupState.Disabled -> {
context?.let { context ->
startActivityForResult(KeysBackupSetupActivity.intent(context, getExtraMatrixID(), true), EXPORT_REQ)
}
}
KeysBackupStateManager.KeysBackupState.BackingUp,
KeysBackupStateManager.KeysBackupState.WillBackUp -> {
//keys are already backing up please wait
context?.toast(R.string.keys_backup_is_not_finished_please_wait)
}
else -> {
//nop
}
}
*/
}
.setNegativeButton(R.string.action_sign_out) { _, _ ->
onSignOut?.run()
}
.show()
}
}
viewModel.keysExportedToFile.observe(this, Observer {
val hasExportedToFile = it ?: false
if (hasExportedToFile) {
//We can allow to sign out
sheetTitle.text = getString(R.string.action_sign_out_confirmation_simple)
signoutClickableView.isVisible = true
dontWantClickableView.isVisible = false
setupClickableView.isVisible = false
activateClickableView.isVisible = false
backingUpStatusGroup.isVisible = false
}
})
/* TODO
viewModel.keysBackupState.observe(this, Observer {
if (viewModel.keysExportedToFile.value == true) {
//ignore this
return@Observer
}
TransitionManager.beginDelayedTransition(rootLayout)
when (it) {
KeysBackupStateManager.KeysBackupState.ReadyToBackUp -> {
signoutClickableView.isVisible = true
dontWantClickableView.isVisible = false
setupClickableView.isVisible = false
activateClickableView.isVisible = false
backingUpStatusGroup.isVisible = true
backupProgress.isVisible = false
backupCompleteImage.isVisible = true
backupStatusTex.text = getString(R.string.keys_backup_info_keys_all_backup_up)
sheetTitle.text = getString(R.string.action_sign_out_confirmation_simple)
}
KeysBackupStateManager.KeysBackupState.BackingUp,
KeysBackupStateManager.KeysBackupState.WillBackUp -> {
backingUpStatusGroup.isVisible = true
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backing_up)
dontWantClickableView.isVisible = true
setupClickableView.isVisible = false
activateClickableView.isVisible = false
backupProgress.isVisible = true
backupCompleteImage.isVisible = false
backupStatusTex.text = getString(R.string.sign_out_bottom_sheet_backing_up_keys)
}
KeysBackupStateManager.KeysBackupState.NotTrusted -> {
backingUpStatusGroup.isVisible = false
dontWantClickableView.isVisible = true
setupClickableView.isVisible = false
activateClickableView.isVisible = true
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_backup_not_active)
}
else -> {
backingUpStatusGroup.isVisible = false
dontWantClickableView.isVisible = true
setupClickableView.isVisible = true
activateClickableView.isVisible = false
sheetTitle.text = getString(R.string.sign_out_bottom_sheet_warning_no_backup)
}
}
// updateSignOutSection()
})
*/
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.bottom_sheet_logout_and_backup, container, false)
ButterKnife.bind(this, view)
return view
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
//We want to force the bottom sheet initial state to expanded
(dialog as? BottomSheetDialog)?.let { bottomSheetDialog ->
bottomSheetDialog.setOnShowListener { dialog ->
val d = dialog as BottomSheetDialog
(d.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet) as? FrameLayout)?.let {
BottomSheetBehavior.from(it).state = BottomSheetBehavior.STATE_EXPANDED
}
}
}
return dialog
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
/* TODO
if (resultCode == Activity.RESULT_OK) {
if (requestCode == EXPORT_REQ) {
val manualExportDone = data?.getBooleanExtra(KeysBackupSetupActivity.MANUAL_EXPORT, false)
viewModel.keysExportedToFile.value = manualExportDone
}
}
*/
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.features.workers.signout
import android.content.Intent
import androidx.appcompat.app.AlertDialog
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.RiotActivity
import im.vector.riotredesign.features.MainActivity
class SignOutUiWorker(val activity: RiotActivity) {
fun perform(session: Session) {
if (SignOutViewModel.doYouNeedToBeDisplayed(session)) {
val signOutDialog = SignOutBottomSheetDialogFragment.newInstance(session.sessionParams.credentials.userId)
signOutDialog.onSignOut = Runnable {
doSignOut(session)
}
signOutDialog.show(activity.supportFragmentManager, "SO")
} else {
// Display a simple confirmation dialog
AlertDialog.Builder(activity)
.setTitle(R.string.action_sign_out)
.setMessage(R.string.action_sign_out_confirmation_simple)
.setPositiveButton(R.string.action_sign_out) { _, _ ->
doSignOut(session)
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}
private fun doSignOut(session: Session) {
// TODO showWaitingView()
session.signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// Start MainActivity in a new task
val intent = Intent(activity, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
activity.startActivity(intent)
}
override fun onFailure(failure: Throwable) {
// TODO Notify user, or ignore?
}
})
}
}

View file

@ -0,0 +1,110 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.features.workers.signout
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import im.vector.matrix.android.api.session.Session
class SignOutViewModel : ViewModel() { // TODO, KeysBackupStateManager.KeysBackupStateListener {
// Keys exported manually
var keysExportedToFile = MutableLiveData<Boolean>()
// var keysBackupState = MutableLiveData<KeysBackupStateManager.KeysBackupState>()
private var mxSession: Session? = null
fun init(session: Session) {
if (mxSession == null) {
mxSession = session
// TODO
//mxSession?.crypto
// ?.keysBackup
// ?.addListener(this)
}
//keysBackupState.value = mxSession?.crypto
// ?.keysBackup
// ?.state
}
// /**
// * Safe way to get the current KeysBackup version
// */
// fun getCurrentBackupVersion(): String {
// return mxSession
// ?.crypto
// ?.keysBackup
// ?.currentBackupVersion
// ?: ""
// }
//
// /**
// * Safe way to get the number of keys to backup
// */
// fun getNumberOfKeysToBackup(): Int {
// return mxSession
// ?.crypto
// ?.cryptoStore
// ?.inboundGroupSessionsCount(false)
// ?: 0
// }
//
// /**
// * Safe way to tell if there are more keys on the server
// */
// fun canRestoreKeys(): Boolean {
// return mxSession
// ?.crypto
// ?.keysBackup
// ?.canRestoreKeys() == true
// }
//
// override fun onCleared() {
// super.onCleared()
//
// mxSession?.crypto
// ?.keysBackup
// ?.removeListener(this)
// }
//
// override fun onStateChange(newState: KeysBackupStateManager.KeysBackupState) {
// keysBackupState.value = newState
// }
companion object {
/**
* 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
*/
fun doYouNeedToBeDisplayed(session: Session?): Boolean {
return false
/* TODO
return session
?.crypto
?.cryptoStore
?.inboundGroupSessionsCount(false)
?: 0 > 0
&& session
?.crypto
?.keysBackup
?.state != KeysBackupStateManager.KeysBackupState.ReadyToBackUp
*/
}
}
}

View file

@ -0,0 +1,202 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginBottom="8dp"
android:text="@string/action_sign_out"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/bottom_sheet_signout_warning_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginBottom="8dp"
android:textColor="?android:attr/textColorSecondary"
tools:text="@string/sign_out_bottom_sheet_warning_no_backup" />
<LinearLayout
android:id="@+id/bottom_sheet_signout_backingup_status_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginBottom="8dp"
android:gravity="center"
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:id="@+id/bottom_sheet_signout_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:visibility="gone"
app:srcCompat="@drawable/unit_test_ok"
tools:visibility="visible" />
<ProgressBar
android:id="@+id/bottom_sheet_signout_icon_progress_bar"
style="?android:attr/progressBarStyle"
android:layout_width="20dp"
android:layout_height="20dp"
android:visibility="visible"
tools:visibility="gone" />
<TextView
android:id="@+id/bottom_sheet_backup_status_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
tools:text="@string/keys_backup_info_keys_all_backup_up" />
</LinearLayout>
<LinearLayout
android:id="@+id/keys_backup_setup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="50dp"
android:orientation="horizontal"
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"
android:paddingBottom="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:scaleType="fitCenter"
android:src="@drawable/backup_keys"
android:tint="?android:attr/textColorTertiary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/keys_backup_setup"
android:textSize="17sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/keys_backup_activate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="50dp"
android:orientation="horizontal"
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"
android:paddingBottom="8dp"
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:scaleType="fitCenter"
android:src="@drawable/backup_keys"
android:tint="?android:attr/textColorTertiary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/keys_backup_activate"
android:textSize="17sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/keys_backup_dont_want"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="50dp"
android:orientation="horizontal"
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"
android:paddingBottom="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_material_leave"
android:tint="@color/vector_error_color" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/sign_out_bottom_sheet_dont_want_secure_messages"
android:textColor="@color/vector_error_color"
android:textSize="17sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_sheet_signout_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="50dp"
android:orientation="horizontal"
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"
android:paddingBottom="8dp"
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:src="@drawable/ic_material_exit_to_app"
android:tint="@color/vector_error_color" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/action_sign_out"
android:textColor="@color/vector_error_color"
android:textSize="17sp" />
</LinearLayout>
</LinearLayout>

View file

@ -1,11 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<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">
<item <item
android:id="@+id/sliding_menu_settings" android:id="@+id/sliding_menu_settings"
android:icon="@drawable/ic_settings" android:icon="@drawable/ic_settings"
android:title="@string/room_sliding_menu_settings" android:title="@string/room_sliding_menu_settings" />
app:showAsAction="ifRoom" />
<item
android:id="@+id/sliding_menu_sign_out"
android:icon="@drawable/ic_material_exit_to_app"
android:title="@string/action_sign_out" />
</menu> </menu>