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
* @param data the data successfuly returned from the async function
* @param data the data successfully returned from the async function
*/
fun onSuccess(data: T) {
//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.group.GroupService
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

/**
* This interface defines interactions with a session.
* 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

View File

@ -26,16 +26,16 @@ interface ReadService {
/**
* 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.
*/
fun setReadReceipt(eventId: String, callback: MatrixCallback<Void>)
fun setReadReceipt(eventId: String, callback: MatrixCallback<Unit>)

/**
* 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 delete()
}

View File

@ -17,13 +17,13 @@
package im.vector.matrix.android.internal.auth.db

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.internal.auth.SessionParamsStore
import io.realm.Realm
import io.realm.RealmConfiguration

internal class RealmSessionParamsStore(private val mapper: SessionParamsMapper,
private val realmConfiguration: RealmConfiguration) : SessionParamsStore {
private val realmConfiguration: RealmConfiguration) : SessionParamsStore {

override fun save(sessionParams: SessionParams): Try<SessionParams> {
return Try {
@ -50,4 +50,14 @@ internal class RealmSessionParamsStore(private val mapper: SessionParamsMapper,
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.lifecycle.LiveData
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.session.Session
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.RoomService
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.model.User
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.di.MatrixKoinHolder
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.signout.SignOutModule
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.user.UserModule
@ -57,6 +61,7 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
private val roomService by inject<RoomService>()
private val groupService by inject<GroupService>()
private val userService by inject<UserService>()
private val signOutService by inject<SignOutService>()
private val syncThread by inject<SyncThread>()
private val contentUrlResolver by inject<ContentUrlResolver>()
private var isOpen = false
@ -70,8 +75,9 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
val syncModule = SyncModule().definition
val roomModule = RoomModule().definition
val groupModule = GroupModule().definition
val signOutModule = SignOutModule().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)
if (!monarchy.isMonarchyThreadOpen) {
monarchy.openManually()
@ -94,6 +100,23 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
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 {
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.group.GroupService
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.internal.database.LiveEntityObserver
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.RoomMemberDisplayNameResolver
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.UserEntityUpdater
import im.vector.matrix.android.internal.util.md5
@ -102,6 +104,10 @@ internal class SessionModule(private val sessionParams: SessionParams) {
DefaultGroupService(get()) as GroupService
}

scope(DefaultSession.SCOPE) {
DefaultSignOutService(get(), get()) as SignOutService
}

scope(DefaultSession.SCOPE) {
DefaultUserService(get()) as UserService
}

View File

@ -30,20 +30,20 @@ internal class DefaultReadService(private val roomId: String,
private val setReadMarkersTask: SetReadMarkersTask,
private val taskExecutor: TaskExecutor) : ReadService {

override fun markAllAsRead(callback: MatrixCallback<Void>) {
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
val latestEvent = getLatestEvent()
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)
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)
setReadMarkersTask.configureWith(params).executeBy(taskExecutor)
setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
}

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.fragment.app.FragmentManager
import com.airbnb.mvrx.viewModel
import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.hideKeyboard
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.VectorUncaughtExceptionHandler
import im.vector.riotredesign.features.settings.VectorSettingsActivity
import im.vector.riotredesign.features.workers.signout.SignOutUiWorker
import kotlinx.android.synthetic.main.activity_home.*
import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope
@ -114,6 +116,10 @@ class HomeActivity : RiotActivity(), ToolbarConfigurable {
startActivity(VectorSettingsActivity.getIntent(this, "TODO"))
return true
}
R.id.sliding_menu_sign_out -> {
SignOutUiWorker(this).perform(Matrix.getInstance().currentSession!!)
return true
}
}

return true

View File

@ -97,7 +97,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
.subscribeBy(onNext = { actions ->
val mostRecentEvent = actions.maxBy { it.event.displayIndex }
mostRecentEvent?.event?.root?.eventId?.let { eventId ->
room.setReadReceipt(eventId, callback = object : MatrixCallback<Void> {})
room.setReadReceipt(eventId, callback = object : MatrixCallback<Unit> {})
}
})
.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"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<item
android:id="@+id/sliding_menu_settings"
android:icon="@drawable/ic_settings"
android:title="@string/room_sliding_menu_settings"
app:showAsAction="ifRoom" />
android:title="@string/room_sliding_menu_settings" />

<item
android:id="@+id/sliding_menu_sign_out"
android:icon="@drawable/ic_material_exit_to_app"
android:title="@string/action_sign_out" />

</menu>