From 08dacacdda8785d3af228862c36934e6c146c61f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 3 Apr 2019 16:36:45 +0200 Subject: [PATCH] SignOut --- .../matrix/android/api/MatrixCallback.kt | 2 +- .../matrix/android/api/session/Session.kt | 8 +- .../api/session/room/read/ReadService.kt | 6 +- .../api/session/signout/SignOutService.kt | 31 +++ .../internal/auth/SessionParamsStore.kt | 1 + .../auth/db/RealmSessionParamsStore.kt | 14 +- .../internal/session/DefaultSession.kt | 25 +- .../android/internal/session/SessionModule.kt | 6 + .../session/room/read/DefaultReadService.kt | 12 +- .../session/signout/DefaultSignOutService.kt | 34 +++ .../internal/session/signout/SignOutAPI.kt | 31 +++ .../internal/session/signout/SignOutModule.kt | 37 +++ .../internal/session/signout/SignOutTask.kt | 38 +++ .../features/home/HomeActivity.kt | 6 + .../home/room/detail/RoomDetailViewModel.kt | 2 +- .../SignOutBottomSheetDialogFragment.kt | 257 ++++++++++++++++++ .../workers/signout/SignOutUiWorker.kt | 69 +++++ .../workers/signout/SignOutViewModel.kt | 110 ++++++++ .../layout/bottom_sheet_logout_and_backup.xml | 202 ++++++++++++++ vector/src/main/res/menu/home.xml | 11 +- 20 files changed, 883 insertions(+), 19 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutAPI.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutModule.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutBottomSheetDialogFragment.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutUiWorker.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutViewModel.kt create mode 100644 vector/src/main/res/layout/bottom_sheet_logout_and_backup.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixCallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixCallback.kt index 961373ae..00d22b1f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixCallback.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/MatrixCallback.kt @@ -24,7 +24,7 @@ interface MatrixCallback { /** * 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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index e5ee1c0e..5c09ac8a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt index b7b78bc8..ed67c1db 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/read/ReadService.kt @@ -26,16 +26,16 @@ interface ReadService { /** * Force the read marker to be set on the latest event. */ - fun markAllAsRead(callback: MatrixCallback) + fun markAllAsRead(callback: MatrixCallback) /** * Set the read receipt on the event with provided eventId. */ - fun setReadReceipt(eventId: String, callback: MatrixCallback) + fun setReadReceipt(eventId: String, callback: MatrixCallback) /** * Set the read marker on the event with provided eventId. */ - fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback) + fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback) } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt new file mode 100644 index 00000000..11f7cd57 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/signout/SignOutService.kt @@ -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) + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt index d742d782..fa9c73a9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/SessionParamsStore.kt @@ -25,4 +25,5 @@ internal interface SessionParamsStore { fun save(sessionParams: SessionParams): Try + fun delete() } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt index b8d56eb3..e621c6d0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/db/RealmSessionParamsStore.kt @@ -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 { 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() + } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index bace6740..1959cb8d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -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() private val groupService by inject() private val userService by inject() + private val signOutService by inject() private val syncThread by inject() private val contentUrlResolver by inject() 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) { + assert(isOpen) + return signOutService.signOut(object : MatrixCallback { + 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 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 85dcdb03..bc13d2e7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -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 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt index 467e6117..e1ca59e5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/DefaultReadService.kt @@ -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) { + override fun markAllAsRead(callback: MatrixCallback) { 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) { + override fun setReadReceipt(eventId: String, callback: MatrixCallback) { 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) { + override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback) { 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? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt new file mode 100644 index 00000000..ff399900 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/DefaultSignOutService.kt @@ -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) { + signOutTask + .configureWith(Unit) + .dispatchTo(callback) + .executeBy(taskExecutor) + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutAPI.kt new file mode 100644 index 00000000..cf45c8d9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutAPI.kt @@ -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 + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutModule.kt new file mode 100644 index 00000000..98098b1a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutModule.kt @@ -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 + } + + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt new file mode 100644 index 00000000..7ef65e70 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/signout/SignOutTask.kt @@ -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 + + +internal class DefaultSignOutTask(private val signOutAPI: SignOutAPI, + private val sessionParamsStore: SessionParamsStore) : SignOutTask { + + override fun execute(params: Unit): Try { + return executeRequest { + apiCall = signOutAPI.signOut() + }.map { + // TODO Clear DB, media cache, etc. + sessionParamsStore.delete() + } + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index 206a7e8b..26443186 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt index 4ebf4984..d38465f4 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt @@ -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 {}) + room.setReadReceipt(eventId, callback = object : MatrixCallback {}) } }) .disposeOnClear() diff --git a/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutBottomSheetDialogFragment.kt new file mode 100644 index 00000000..eb5fdcba --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutBottomSheetDialogFragment.kt @@ -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() + + @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(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 + } + } + */ + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutUiWorker.kt b/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutUiWorker.kt new file mode 100644 index 00000000..868345eb --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutUiWorker.kt @@ -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 { + + 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? + } + + }) + + } +} diff --git a/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutViewModel.kt new file mode 100644 index 00000000..b26adbd1 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/workers/signout/SignOutViewModel.kt @@ -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() + + // var keysBackupState = MutableLiveData() + + 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 + */ + } + } +} \ No newline at end of file diff --git a/vector/src/main/res/layout/bottom_sheet_logout_and_backup.xml b/vector/src/main/res/layout/bottom_sheet_logout_and_backup.xml new file mode 100644 index 00000000..6a86c0be --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_logout_and_backup.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/menu/home.xml b/vector/src/main/res/menu/home.xml index 5ec8b92e..74cf0169 100644 --- a/vector/src/main/res/menu/home.xml +++ b/vector/src/main/res/menu/home.xml @@ -1,11 +1,14 @@ - + + android:title="@string/room_sliding_menu_settings" /> + + \ No newline at end of file