forked from GitHub-Mirror/riotX-android
Keys backup: migrate settings to Epoxy and MvRx
This commit is contained in:
parent
53dd9c3427
commit
b47ef9220e
@ -18,11 +18,11 @@ package im.vector.riotredesign
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import androidx.core.provider.FontRequest
|
||||
import androidx.core.provider.FontsContractCompat
|
||||
import android.content.res.Configuration
|
||||
import androidx.multidex.MultiDex
|
||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
@ -33,6 +33,7 @@ import com.jakewharton.threetenabp.AndroidThreeTen
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.riotredesign.core.di.AppModule
|
||||
import im.vector.riotredesign.features.configuration.VectorConfiguration
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.KeysBackupModule
|
||||
import im.vector.riotredesign.features.home.HomeModule
|
||||
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
|
||||
import im.vector.riotredesign.features.rageshake.VectorFileLogger
|
||||
@ -73,7 +74,8 @@ class VectorApplication : Application() {
|
||||
val appModule = AppModule(applicationContext).definition
|
||||
val homeModule = HomeModule().definition
|
||||
val roomDirectoryModule = RoomDirectoryModule().definition
|
||||
val koin = startKoin(listOf(appModule, homeModule, roomDirectoryModule), logger = EmptyLogger())
|
||||
val keysBackupModule = KeysBackupModule().definition
|
||||
val koin = startKoin(listOf(appModule, homeModule, roomDirectoryModule, keysBackupModule), logger = EmptyLogger())
|
||||
Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION)
|
||||
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks())
|
||||
|
||||
|
@ -20,7 +20,7 @@ import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
|
||||
/**
|
||||
* Set a text in the TextView, or set visibility to GONE it if the text is null
|
||||
* Set a text in the TextView, or set visibility to GONE if the text is null
|
||||
*/
|
||||
fun TextView.setTextOrHide(newText: String?, hideWhenBlank: Boolean = true) {
|
||||
if (newText == null
|
||||
|
@ -18,6 +18,7 @@ package im.vector.riotredesign.core.resources
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.NonNull
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
class StringProvider(private val resources: Resources) {
|
||||
@ -51,5 +52,9 @@ class StringProvider(private val resources: Resources) {
|
||||
return resources.getString(resId, *formatArgs)
|
||||
}
|
||||
|
||||
@NonNull
|
||||
fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String {
|
||||
return resources.getQuantityString(resId, quantity, *formatArgs)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.core.ui.list
|
||||
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotredesign.core.extensions.setTextOrHide
|
||||
|
||||
/**
|
||||
* A generic list item.
|
||||
* Displays an item with a title, and optional description.
|
||||
* Can display an accessory on the right, that can be an image or an indeterminate progress.
|
||||
* If provided with an action, will display a button at the bottom of the list item.
|
||||
*/
|
||||
@EpoxyModelClass(layout = R.layout.item_generic_list)
|
||||
abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
|
||||
|
||||
enum class STYLE {
|
||||
BIG_TEXT,
|
||||
NORMAL_TEXT
|
||||
}
|
||||
|
||||
class Action(var title: String) {
|
||||
var perform: Runnable? = null
|
||||
}
|
||||
|
||||
@EpoxyAttribute
|
||||
var title: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var description: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var style: STYLE = STYLE.NORMAL_TEXT
|
||||
|
||||
@EpoxyAttribute
|
||||
@DrawableRes
|
||||
var endIconResourceId: Int = -1
|
||||
|
||||
@EpoxyAttribute
|
||||
var hasIndeterminateProcess = false
|
||||
|
||||
@EpoxyAttribute
|
||||
var buttonAction: Action? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var itemClickAction: Action? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
holder.titleText.setTextOrHide(title)
|
||||
|
||||
when (style) {
|
||||
STYLE.BIG_TEXT -> holder.titleText.textSize = 18f
|
||||
STYLE.NORMAL_TEXT -> holder.titleText.textSize = 14f
|
||||
}
|
||||
|
||||
holder.descriptionText.setTextOrHide(description)
|
||||
|
||||
if (hasIndeterminateProcess) {
|
||||
holder.progressBar.isVisible = true
|
||||
holder.accessoryImage.isVisible = false
|
||||
} else {
|
||||
holder.progressBar.isVisible = false
|
||||
if (endIconResourceId != -1) {
|
||||
holder.accessoryImage.setImageResource(endIconResourceId)
|
||||
holder.accessoryImage.isVisible = true
|
||||
} else {
|
||||
holder.accessoryImage.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
holder.actionButton.setTextOrHide(buttonAction?.title)
|
||||
holder.actionButton.setOnClickListener {
|
||||
buttonAction?.perform?.run()
|
||||
}
|
||||
|
||||
holder.root.setOnClickListener {
|
||||
itemClickAction?.perform?.run()
|
||||
}
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val root by bind<View>(R.id.item_generic_root)
|
||||
|
||||
val titleText by bind<TextView>(R.id.item_generic_title_text)
|
||||
val descriptionText by bind<TextView>(R.id.item_generic_description_text)
|
||||
val accessoryImage by bind<ImageView>(R.id.item_generic_accessory_image)
|
||||
val progressBar by bind<ProgressBar>(R.id.item_generic_progress_bar)
|
||||
val actionButton by bind<Button>(R.id.item_generic_action_button)
|
||||
}
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
/*
|
||||
* 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.core.ui.list
|
||||
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import im.vector.riotredesign.R
|
||||
|
||||
/**
|
||||
* View Holder for generic list items.
|
||||
* Displays an item with a title, and optional description.
|
||||
* Can display an accessory on the right, that can be an image or an indeterminate progress.
|
||||
* If provided with an action, will display a button at the bottom of the list item.
|
||||
*/
|
||||
class GenericItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
companion object {
|
||||
@LayoutRes
|
||||
const val resId = R.layout.item_generic_list
|
||||
}
|
||||
|
||||
@BindView(R.id.item_generic_title_text)
|
||||
lateinit var titleText: TextView
|
||||
|
||||
@BindView(R.id.item_generic_description_text)
|
||||
lateinit var descriptionText: TextView
|
||||
|
||||
@BindView(R.id.item_generic_accessory_image)
|
||||
lateinit var accessoryImage: ImageView
|
||||
|
||||
@BindView(R.id.item_generic_progress_bar)
|
||||
lateinit var progressBar: ProgressBar
|
||||
|
||||
@BindView(R.id.item_generic_action_button)
|
||||
lateinit var actionButton: Button
|
||||
|
||||
init {
|
||||
ButterKnife.bind(this, itemView)
|
||||
}
|
||||
|
||||
fun bind(item: GenericRecyclerViewItem) {
|
||||
titleText.text = item.title
|
||||
|
||||
when (item.style) {
|
||||
GenericRecyclerViewItem.STYLE.BIG_TEXT -> titleText.textSize = 18f
|
||||
GenericRecyclerViewItem.STYLE.NORMAL_TEXT -> titleText.textSize = 14f
|
||||
}
|
||||
|
||||
item.description?.let {
|
||||
descriptionText.isVisible = true
|
||||
descriptionText.text = it
|
||||
} ?: run { descriptionText.isVisible = false }
|
||||
|
||||
if (item.hasIndeterminateProcess) {
|
||||
progressBar.isVisible = true
|
||||
accessoryImage.isVisible = false
|
||||
} else {
|
||||
progressBar.isVisible = false
|
||||
if (item.endIconResourceId != -1) {
|
||||
accessoryImage.setImageResource(item.endIconResourceId)
|
||||
accessoryImage.isVisible = true
|
||||
} else {
|
||||
accessoryImage.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
val buttonAction = item.buttonAction
|
||||
|
||||
if (buttonAction == null) {
|
||||
actionButton.isVisible = false
|
||||
} else {
|
||||
actionButton.text = buttonAction.title
|
||||
actionButton.setOnClickListener {
|
||||
buttonAction.perform?.run()
|
||||
}
|
||||
actionButton.isVisible = true
|
||||
}
|
||||
|
||||
itemView?.setOnClickListener {
|
||||
item.itemClickAction?.perform?.run()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.core.ui.list
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
|
||||
|
||||
/**
|
||||
* A generic list item.
|
||||
* Displays an item with a title, and optional description.
|
||||
* Can display an accessory on the right, that can be an image or an indeterminate progress.
|
||||
* If provided with an action, will display a button at the bottom of the list item.
|
||||
*/
|
||||
class GenericRecyclerViewItem(val title: String,
|
||||
var description: String? = null,
|
||||
val style: STYLE = STYLE.NORMAL_TEXT) {
|
||||
|
||||
enum class STYLE {
|
||||
BIG_TEXT,
|
||||
NORMAL_TEXT
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
var endIconResourceId: Int = -1
|
||||
|
||||
var hasIndeterminateProcess = false
|
||||
|
||||
var buttonAction: Action? = null
|
||||
|
||||
var itemClickAction: Action? = null
|
||||
|
||||
class Action(var title: String) {
|
||||
var perform: Runnable? = null
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.crypto.keysbackup
|
||||
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupSettingsRecyclerViewController
|
||||
import org.koin.dsl.module.module
|
||||
|
||||
class KeysBackupModule {
|
||||
|
||||
companion object {
|
||||
const val KEYS_BACKUP_SCOPE = "KEYS_BACKUP_SCOPE"
|
||||
}
|
||||
|
||||
val definition = module(override = true) {
|
||||
|
||||
scope(KEYS_BACKUP_SCOPE) {
|
||||
KeysBackupSettingsRecyclerViewController(get(), get())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -18,12 +18,15 @@ package im.vector.riotredesign.features.crypto.keysbackup.settings
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import im.vector.fragments.keysbackup.settings.KeysBackupSettingsFragment
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.SimpleFragmentActivity
|
||||
import im.vector.riotredesign.core.platform.WaitingViewData
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.KeysBackupModule
|
||||
import org.koin.android.scope.ext.android.bindScope
|
||||
import org.koin.android.scope.ext.android.getOrCreateScope
|
||||
|
||||
|
||||
class KeysBackupManageActivity : SimpleFragmentActivity() {
|
||||
@ -31,46 +34,47 @@ class KeysBackupManageActivity : SimpleFragmentActivity() {
|
||||
companion object {
|
||||
|
||||
fun intent(context: Context): Intent {
|
||||
val intent = Intent(context, KeysBackupManageActivity::class.java)
|
||||
return intent
|
||||
return Intent(context, KeysBackupManageActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTitleRes() = R.string.encryption_message_recovery
|
||||
|
||||
|
||||
private lateinit var viewModel: KeysBackupSettingsViewModel
|
||||
private val viewModel: KeysBackupSettingsViewModel by viewModel()
|
||||
|
||||
override fun initUiAndData() {
|
||||
super.initUiAndData()
|
||||
viewModel = ViewModelProviders.of(this).get(KeysBackupSettingsViewModel::class.java)
|
||||
viewModel.initSession(session)
|
||||
|
||||
bindScope(getOrCreateScope(KeysBackupModule.KEYS_BACKUP_SCOPE))
|
||||
|
||||
if (supportFragmentManager.fragments.isEmpty()) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, KeysBackupSettingsFragment.newInstance())
|
||||
.commitNow()
|
||||
|
||||
session.getKeysBackupService()
|
||||
.forceUsingLastVersion(object : MatrixCallback<Boolean> {})
|
||||
viewModel.init()
|
||||
}
|
||||
|
||||
viewModel.loadingEvent.observe(this, Observer {
|
||||
updateWaitingView(it)
|
||||
})
|
||||
// Observe the deletion of keys backup
|
||||
viewModel.selectSubscribe(this, KeysBackupSettingViewState::deleteBackupRequest) { asyncDelete ->
|
||||
when (asyncDelete) {
|
||||
is Fail -> {
|
||||
updateWaitingView(null)
|
||||
|
||||
|
||||
viewModel.apiResultError.observe(this, Observer { uxStateEvent ->
|
||||
uxStateEvent?.getContentIfNotHandled()?.let {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.unknown_error)
|
||||
.setMessage(it)
|
||||
.setMessage(getString(R.string.keys_backup_get_version_error, asyncDelete.error.localizedMessage))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
})
|
||||
|
||||
is Loading -> {
|
||||
updateWaitingView(WaitingViewData(getString(R.string.keys_backup_settings_deleting_backup)))
|
||||
}
|
||||
else -> {
|
||||
updateWaitingView(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.crypto.keysbackup.settings
|
||||
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotredesign.core.extensions.setTextOrHide
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_keys_backup_settings_button_footer)
|
||||
abstract class KeysBackupSettingFooterItem : VectorEpoxyModel<KeysBackupSettingFooterItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var textButton1: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var clickOnButton1: View.OnClickListener? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var textButton2: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var clickOnButton2: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
holder.button1.setTextOrHide(textButton1)
|
||||
holder.button1.setOnClickListener(clickOnButton1)
|
||||
|
||||
holder.button2.setTextOrHide(textButton2)
|
||||
holder.button2.setOnClickListener(clickOnButton2)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val button1 by bind<Button>(R.id.keys_backup_settings_footer_button1)
|
||||
val button2 by bind<TextView>(R.id.keys_backup_settings_footer_button2)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.crypto.keysbackup.settings
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
|
||||
data class KeysBackupSettingViewState(val keysBackupVersionTrust: Async<KeysBackupVersionTrust> = Uninitialized,
|
||||
val keysBackupState: KeysBackupState? = null,
|
||||
val keysBackupVersion: KeysVersionResult? = null,
|
||||
val deleteBackupRequest: Async<Unit> = Uninitialized)
|
||||
: MvRxState
|
@ -13,27 +13,25 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.fragments.keysbackup.settings
|
||||
package im.vector.riotredesign.features.crypto.keysbackup.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import butterknife.BindView
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
||||
import im.vector.riotredesign.core.platform.WaitingViewData
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.KeysBackupModule
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel
|
||||
import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
||||
import kotlinx.android.synthetic.main.fragment_keys_backup_settings.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.android.scope.ext.android.bindScope
|
||||
import org.koin.android.scope.ext.android.getOrCreateScope
|
||||
|
||||
class KeysBackupSettingsFragment : VectorBaseFragment(),
|
||||
KeysBackupSettingsRecyclerViewAdapter.AdapterListener {
|
||||
|
||||
KeysBackupSettingsRecyclerViewController.Listener {
|
||||
|
||||
companion object {
|
||||
fun newInstance() = KeysBackupSettingsFragment()
|
||||
@ -41,63 +39,26 @@ class KeysBackupSettingsFragment : VectorBaseFragment(),
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_keys_backup_settings
|
||||
|
||||
private lateinit var viewModel: KeysBackupSettingsViewModel
|
||||
private val keysBackupSettingsRecyclerViewController: KeysBackupSettingsRecyclerViewController by inject()
|
||||
|
||||
@BindView(R.id.keys_backup_settings_recycler_view)
|
||||
lateinit var recyclerView: RecyclerView
|
||||
private val viewModel: KeysBackupSettingsViewModel by activityViewModel()
|
||||
|
||||
private var recyclerViewAdapter: KeysBackupSettingsRecyclerViewAdapter? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
bindScope(getOrCreateScope(KeysBackupModule.KEYS_BACKUP_SCOPE))
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val layoutManager = LinearLayoutManager(context)
|
||||
recyclerView.layoutManager = layoutManager
|
||||
keysBackupSettingsRecyclerView.setController(keysBackupSettingsRecyclerViewController)
|
||||
|
||||
recyclerViewAdapter = KeysBackupSettingsRecyclerViewAdapter(activity!!)
|
||||
recyclerView.adapter = recyclerViewAdapter
|
||||
recyclerViewAdapter?.adapterListener = this
|
||||
|
||||
|
||||
viewModel = activity?.run {
|
||||
ViewModelProviders.of(this).get(KeysBackupSettingsViewModel::class.java)
|
||||
} ?: throw Exception("Invalid Activity")
|
||||
|
||||
|
||||
viewModel.keyBackupState.observe(this, Observer { keysBackupState ->
|
||||
if (keysBackupState == null) {
|
||||
//Cannot happen?
|
||||
viewModel.keyVersionTrust.value = null
|
||||
} else {
|
||||
when (keysBackupState) {
|
||||
KeysBackupState.Unknown,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver -> {
|
||||
viewModel.loadingEvent.value = WaitingViewData("")
|
||||
}
|
||||
else -> {
|
||||
viewModel.loadingEvent.value = null
|
||||
//All this cases will be manage by looking at the backup trust object
|
||||
viewModel.session?.getKeysBackupService()?.keysBackupVersion?.let {
|
||||
viewModel.getKeysBackupTrust(it)
|
||||
} ?: run {
|
||||
viewModel.keyVersionTrust.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
keysBackupSettingsRecyclerViewController.listener = this
|
||||
}
|
||||
|
||||
// Update the adapter for each state change
|
||||
viewModel.session?.let { session ->
|
||||
recyclerViewAdapter?.updateWithTrust(session, viewModel.keyVersionTrust.value)
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.keyVersionTrust.observe(this, Observer {
|
||||
viewModel.session?.let { session ->
|
||||
recyclerViewAdapter?.updateWithTrust(session, it)
|
||||
}
|
||||
})
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
keysBackupSettingsRecyclerViewController.setData(state)
|
||||
}
|
||||
|
||||
override fun didSelectSetupMessageRecovery() {
|
||||
@ -127,4 +88,11 @@ class KeysBackupSettingsFragment : VectorBaseFragment(),
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadTrustData() {
|
||||
viewModel.getKeysBackupTrust()
|
||||
}
|
||||
|
||||
override fun loadKeysBackupState() {
|
||||
viewModel.init()
|
||||
}
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
/*
|
||||
* 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.fragments.keysbackup.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.ui.list.GenericItemViewHolder
|
||||
import im.vector.riotredesign.core.ui.list.GenericRecyclerViewItem
|
||||
|
||||
class KeysBackupSettingsRecyclerViewAdapter(val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
|
||||
private var infoList: List<GenericRecyclerViewItem> = ArrayList()
|
||||
|
||||
private var isBackupAlreadySetup = false
|
||||
|
||||
var adapterListener: AdapterListener? = null
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (viewType) {
|
||||
GenericItemViewHolder.resId -> GenericItemViewHolder(inflater.inflate(viewType, parent, false))
|
||||
else -> FooterViewHolder(inflater.inflate(viewType, parent, false))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (position < infoList.size) {
|
||||
GenericItemViewHolder.resId
|
||||
} else {
|
||||
R.layout.item_keys_backup_settings_button_footer
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
if (holder is GenericItemViewHolder) {
|
||||
holder.bind(infoList[position])
|
||||
} else if (holder is FooterViewHolder) {
|
||||
if (isBackupAlreadySetup) {
|
||||
holder.button1.setText(R.string.keys_backup_settings_restore_backup_button)
|
||||
holder.button1.isVisible = true
|
||||
holder.button1.setOnClickListener {
|
||||
adapterListener?.didSelectRestoreMessageRecovery()
|
||||
}
|
||||
|
||||
holder.button2.setText(R.string.keys_backup_settings_delete_backup_button)
|
||||
holder.button2.isVisible = true
|
||||
holder.button2.setOnClickListener {
|
||||
adapterListener?.didSelectDeleteSetupMessageRecovery()
|
||||
}
|
||||
} else {
|
||||
holder.button1.setText(R.string.keys_backup_setup)
|
||||
holder.button1.isVisible = true
|
||||
holder.button1.setOnClickListener {
|
||||
adapterListener?.didSelectSetupMessageRecovery()
|
||||
}
|
||||
|
||||
holder.button2.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return infoList.size + 1 /*footer*/
|
||||
}
|
||||
|
||||
|
||||
fun updateWithTrust(session: Session, keyBackupVersionTrust: KeysBackupVersionTrust?) {
|
||||
val keyBackupState = session.getKeysBackupService().state
|
||||
val keyVersionResult = session.getKeysBackupService().keysBackupVersion
|
||||
|
||||
val infos = ArrayList<GenericRecyclerViewItem>()
|
||||
var itemSummary: GenericRecyclerViewItem? = null
|
||||
|
||||
when (keyBackupState) {
|
||||
KeysBackupState.Unknown,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver -> {
|
||||
//In this cases recycler view is hidden any way
|
||||
//so do nothing
|
||||
}
|
||||
KeysBackupState.Disabled -> {
|
||||
itemSummary = GenericRecyclerViewItem(context.getString(R.string.keys_backup_settings_status_not_setup),
|
||||
style = GenericRecyclerViewItem.STYLE.BIG_TEXT)
|
||||
|
||||
isBackupAlreadySetup = false
|
||||
}
|
||||
KeysBackupState.WrongBackUpVersion,
|
||||
KeysBackupState.NotTrusted,
|
||||
KeysBackupState.Enabling -> {
|
||||
itemSummary = GenericRecyclerViewItem(context.getString(R.string.keys_backup_settings_status_ko),
|
||||
style = GenericRecyclerViewItem.STYLE.BIG_TEXT).apply {
|
||||
description = keyBackupState.toString()
|
||||
endIconResourceId = R.drawable.unit_test_ko
|
||||
}
|
||||
|
||||
isBackupAlreadySetup = true
|
||||
}
|
||||
KeysBackupState.ReadyToBackUp -> {
|
||||
itemSummary = GenericRecyclerViewItem(context.getString(R.string.keys_backup_settings_status_ok),
|
||||
style = GenericRecyclerViewItem.STYLE.BIG_TEXT).apply {
|
||||
endIconResourceId = R.drawable.unit_test_ok
|
||||
description = context.getString(R.string.keys_backup_info_keys_all_backup_up)
|
||||
}
|
||||
|
||||
isBackupAlreadySetup = true
|
||||
}
|
||||
KeysBackupState.WillBackUp,
|
||||
KeysBackupState.BackingUp -> {
|
||||
itemSummary = GenericRecyclerViewItem(context.getString(R.string.keys_backup_settings_status_ok),
|
||||
style = GenericRecyclerViewItem.STYLE.BIG_TEXT).apply {
|
||||
hasIndeterminateProcess = true
|
||||
|
||||
val totalKeys = session.inboundGroupSessionsCount(false)
|
||||
?: 0
|
||||
val backedUpKeys = session.inboundGroupSessionsCount(true)
|
||||
?: 0
|
||||
|
||||
val remainingKeysToBackup = totalKeys - backedUpKeys
|
||||
|
||||
description = context.resources.getQuantityString(R.plurals.keys_backup_info_keys_backing_up, remainingKeysToBackup, remainingKeysToBackup)
|
||||
}
|
||||
|
||||
isBackupAlreadySetup = true
|
||||
}
|
||||
}
|
||||
|
||||
itemSummary?.let {
|
||||
infos.add(it)
|
||||
}
|
||||
|
||||
if (keyBackupVersionTrust != null) {
|
||||
|
||||
if (!keyBackupVersionTrust.usable) {
|
||||
itemSummary?.description = context.getString(R.string.keys_backup_settings_untrusted_backup)
|
||||
}
|
||||
|
||||
//Add infos
|
||||
infos.add(GenericRecyclerViewItem(context.getString(R.string.keys_backup_info_title_version), keyVersionResult?.version
|
||||
?: ""))
|
||||
infos.add(GenericRecyclerViewItem(context.getString(R.string.keys_backup_info_title_algorithm), keyVersionResult?.algorithm
|
||||
?: ""))
|
||||
|
||||
keyBackupVersionTrust.signatures.forEach {
|
||||
val signatureInfo = GenericRecyclerViewItem(context.getString(R.string.keys_backup_info_title_signature))
|
||||
val isDeviceKnown = it.device != null
|
||||
val isDeviceVerified = it.device?.isVerified ?: false
|
||||
val isSignatureValid = it.valid
|
||||
val deviceId: String = it.deviceId ?: ""
|
||||
|
||||
if (!isDeviceKnown) {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_signature_from_unknown_device, deviceId)
|
||||
signatureInfo.endIconResourceId = R.drawable.e2e_warning
|
||||
} else {
|
||||
if (isSignatureValid) {
|
||||
if (session.sessionParams.credentials.deviceId == it.deviceId) {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_valid_signature_from_this_device)
|
||||
signatureInfo.endIconResourceId = R.drawable.e2e_verified
|
||||
} else {
|
||||
if (isDeviceVerified) {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_valid_signature_from_verified_device, deviceId)
|
||||
signatureInfo.endIconResourceId = R.drawable.e2e_verified
|
||||
} else {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_valid_signature_from_unverified_device, deviceId)
|
||||
signatureInfo.endIconResourceId = R.drawable.e2e_warning
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Invalid signature
|
||||
signatureInfo.endIconResourceId = R.drawable.e2e_warning
|
||||
if (isDeviceVerified) {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_invalid_signature_from_verified_device, deviceId)
|
||||
} else {
|
||||
signatureInfo.description = context.getString(R.string.keys_backup_settings_invalid_signature_from_unverified_device, deviceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
infos.add(signatureInfo)
|
||||
} //end for each
|
||||
}
|
||||
|
||||
infoList = infos
|
||||
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
class FooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
init {
|
||||
ButterKnife.bind(this, itemView)
|
||||
}
|
||||
|
||||
@BindView(R.id.keys_backup_settings_footer_button1)
|
||||
lateinit var button1: Button
|
||||
|
||||
@BindView(R.id.keys_backup_settings_footer_button2)
|
||||
lateinit var button2: Button
|
||||
|
||||
fun bind() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
interface AdapterListener {
|
||||
fun didSelectSetupMessageRecovery()
|
||||
fun didSelectRestoreMessageRecovery()
|
||||
fun didSelectDeleteSetupMessageRecovery()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.crypto.keysbackup.settings
|
||||
|
||||
import android.view.View
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.*
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.errorWithRetryItem
|
||||
import im.vector.riotredesign.core.epoxy.loadingItem
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
import im.vector.riotredesign.core.ui.list.GenericItem
|
||||
import im.vector.riotredesign.core.ui.list.genericItem
|
||||
import java.util.*
|
||||
|
||||
class KeysBackupSettingsRecyclerViewController(val stringProvider: StringProvider,
|
||||
val session: Session) : TypedEpoxyController<KeysBackupSettingViewState>() {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
||||
override fun buildModels(data: KeysBackupSettingViewState?) {
|
||||
if (data == null) {
|
||||
return
|
||||
}
|
||||
|
||||
var isBackupAlreadySetup = false
|
||||
|
||||
val keyBackupState = data.keysBackupState
|
||||
val keyVersionResult = data.keysBackupVersion
|
||||
|
||||
when (keyBackupState) {
|
||||
KeysBackupState.Unknown -> {
|
||||
errorWithRetryItem {
|
||||
id("summary")
|
||||
text(stringProvider.getString(R.string.keys_backup_unable_to_get_keys_backup_data))
|
||||
listener { listener?.loadKeysBackupState() }
|
||||
}
|
||||
|
||||
// Nothing else to display
|
||||
return
|
||||
}
|
||||
KeysBackupState.CheckingBackUpOnHomeserver -> {
|
||||
loadingItem {
|
||||
id("summary")
|
||||
}
|
||||
}
|
||||
KeysBackupState.Disabled -> {
|
||||
genericItem {
|
||||
id("summary")
|
||||
title(stringProvider.getString(R.string.keys_backup_settings_status_not_setup))
|
||||
style(GenericItem.STYLE.BIG_TEXT)
|
||||
|
||||
if (data.keysBackupVersionTrust()?.usable == false) {
|
||||
description(stringProvider.getString(R.string.keys_backup_settings_untrusted_backup))
|
||||
}
|
||||
}
|
||||
|
||||
isBackupAlreadySetup = false
|
||||
}
|
||||
KeysBackupState.WrongBackUpVersion,
|
||||
KeysBackupState.NotTrusted,
|
||||
KeysBackupState.Enabling -> {
|
||||
genericItem {
|
||||
id("summary")
|
||||
title(stringProvider.getString(R.string.keys_backup_settings_status_ko))
|
||||
style(GenericItem.STYLE.BIG_TEXT)
|
||||
if (data.keysBackupVersionTrust()?.usable == false) {
|
||||
description(stringProvider.getString(R.string.keys_backup_settings_untrusted_backup))
|
||||
} else {
|
||||
description(keyBackupState.toString())
|
||||
}
|
||||
endIconResourceId(R.drawable.unit_test_ko)
|
||||
}
|
||||
|
||||
isBackupAlreadySetup = true
|
||||
}
|
||||
KeysBackupState.ReadyToBackUp -> {
|
||||
genericItem {
|
||||
id("summary")
|
||||
title(stringProvider.getString(R.string.keys_backup_settings_status_ok))
|
||||
style(GenericItem.STYLE.BIG_TEXT)
|
||||
if (data.keysBackupVersionTrust()?.usable == false) {
|
||||
description(stringProvider.getString(R.string.keys_backup_settings_untrusted_backup))
|
||||
} else {
|
||||
description(stringProvider.getString(R.string.keys_backup_info_keys_all_backup_up))
|
||||
}
|
||||
endIconResourceId(R.drawable.unit_test_ok)
|
||||
}
|
||||
|
||||
isBackupAlreadySetup = true
|
||||
}
|
||||
KeysBackupState.WillBackUp,
|
||||
KeysBackupState.BackingUp -> {
|
||||
genericItem {
|
||||
id("summary")
|
||||
title(stringProvider.getString(R.string.keys_backup_settings_status_ok))
|
||||
style(GenericItem.STYLE.BIG_TEXT)
|
||||
hasIndeterminateProcess(true)
|
||||
|
||||
val totalKeys = session.inboundGroupSessionsCount(false)
|
||||
val backedUpKeys = session.inboundGroupSessionsCount(true)
|
||||
|
||||
val remainingKeysToBackup = totalKeys - backedUpKeys
|
||||
|
||||
if (data.keysBackupVersionTrust()?.usable == false) {
|
||||
description(stringProvider.getString(R.string.keys_backup_settings_untrusted_backup))
|
||||
} else {
|
||||
description(stringProvider.getQuantityString(R.plurals.keys_backup_info_keys_backing_up, remainingKeysToBackup, remainingKeysToBackup))
|
||||
}
|
||||
}
|
||||
|
||||
isBackupAlreadySetup = true
|
||||
}
|
||||
}
|
||||
|
||||
if (isBackupAlreadySetup) {
|
||||
//Add infos
|
||||
genericItem {
|
||||
id("version")
|
||||
title(stringProvider.getString(R.string.keys_backup_info_title_version))
|
||||
description(keyVersionResult?.version ?: "")
|
||||
}
|
||||
|
||||
genericItem {
|
||||
id("algorithm")
|
||||
title(stringProvider.getString(R.string.keys_backup_info_title_algorithm))
|
||||
description(keyVersionResult?.algorithm ?: "")
|
||||
}
|
||||
|
||||
buildKeysBackupTrust(data.keysBackupVersionTrust)
|
||||
}
|
||||
|
||||
// Footer
|
||||
keysBackupSettingFooterItem {
|
||||
id("footer")
|
||||
|
||||
if (isBackupAlreadySetup) {
|
||||
textButton1(stringProvider.getString(R.string.keys_backup_settings_restore_backup_button))
|
||||
clickOnButton1(View.OnClickListener { listener?.didSelectRestoreMessageRecovery() })
|
||||
|
||||
textButton2(stringProvider.getString(R.string.keys_backup_settings_delete_backup_button))
|
||||
clickOnButton2(View.OnClickListener { listener?.didSelectDeleteSetupMessageRecovery() })
|
||||
} else {
|
||||
textButton1(stringProvider.getString(R.string.keys_backup_setup))
|
||||
clickOnButton1(View.OnClickListener { listener?.didSelectSetupMessageRecovery() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildKeysBackupTrust(keysVersionTrust: Async<KeysBackupVersionTrust>) {
|
||||
when (keysVersionTrust) {
|
||||
is Uninitialized -> Unit
|
||||
is Loading -> {
|
||||
loadingItem {
|
||||
id("trust")
|
||||
}
|
||||
}
|
||||
is Success -> {
|
||||
keysVersionTrust().signatures.forEach {
|
||||
genericItem {
|
||||
id(UUID.randomUUID().toString())
|
||||
title(stringProvider.getString(R.string.keys_backup_info_title_signature))
|
||||
|
||||
val isDeviceKnown = it.device != null
|
||||
val isDeviceVerified = it.device?.isVerified ?: false
|
||||
val isSignatureValid = it.valid
|
||||
val deviceId: String = it.deviceId ?: ""
|
||||
|
||||
if (!isDeviceKnown) {
|
||||
description(stringProvider.getString(R.string.keys_backup_settings_signature_from_unknown_device, deviceId))
|
||||
endIconResourceId(R.drawable.e2e_warning)
|
||||
} else {
|
||||
if (isSignatureValid) {
|
||||
if (session.sessionParams.credentials.deviceId == it.deviceId) {
|
||||
description(stringProvider.getString(R.string.keys_backup_settings_valid_signature_from_this_device))
|
||||
endIconResourceId(R.drawable.e2e_verified)
|
||||
} else {
|
||||
if (isDeviceVerified) {
|
||||
description(stringProvider.getString(R.string.keys_backup_settings_valid_signature_from_verified_device, deviceId))
|
||||
endIconResourceId(R.drawable.e2e_verified)
|
||||
} else {
|
||||
description(stringProvider.getString(R.string.keys_backup_settings_valid_signature_from_unverified_device, deviceId))
|
||||
endIconResourceId(R.drawable.e2e_warning)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Invalid signature
|
||||
endIconResourceId(R.drawable.e2e_warning)
|
||||
if (isDeviceVerified) {
|
||||
description(stringProvider.getString(R.string.keys_backup_settings_invalid_signature_from_verified_device, deviceId))
|
||||
} else {
|
||||
description(stringProvider.getString(R.string.keys_backup_settings_invalid_signature_from_unverified_device, deviceId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} //end for each
|
||||
}
|
||||
is Fail -> {
|
||||
errorWithRetryItem {
|
||||
id("trust")
|
||||
text(stringProvider.getString(R.string.keys_backup_unable_to_get_trust_info))
|
||||
listener { listener?.loadTrustData() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun didSelectSetupMessageRecovery()
|
||||
fun didSelectRestoreMessageRecovery()
|
||||
fun didSelectDeleteSetupMessageRecovery()
|
||||
fun loadTrustData()
|
||||
fun loadKeysBackupState()
|
||||
}
|
||||
}
|
@ -16,76 +16,125 @@
|
||||
package im.vector.riotredesign.features.crypto.keysbackup.settings
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.airbnb.mvrx.*
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.WaitingViewData
|
||||
import im.vector.riotredesign.core.utils.LiveEvent
|
||||
import im.vector.riotredesign.core.platform.VectorViewModel
|
||||
import org.koin.android.ext.android.get
|
||||
|
||||
|
||||
class KeysBackupSettingsViewModel : ViewModel(),
|
||||
class KeysBackupSettingsViewModel(initialState: KeysBackupSettingViewState,
|
||||
session: Session) : VectorViewModel<KeysBackupSettingViewState>(initialState),
|
||||
KeysBackupService.KeysBackupStateListener {
|
||||
|
||||
var session: Session? = null
|
||||
companion object : MvRxViewModelFactory<KeysBackupSettingsViewModel, KeysBackupSettingViewState> {
|
||||
|
||||
var keyVersionTrust: MutableLiveData<KeysBackupVersionTrust> = MutableLiveData()
|
||||
var keyBackupState: MutableLiveData<KeysBackupState> = MutableLiveData()
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: KeysBackupSettingViewState): KeysBackupSettingsViewModel? {
|
||||
val session = viewModelContext.activity.get<Session>()
|
||||
|
||||
private var _apiResultError: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
||||
val apiResultError: LiveData<LiveEvent<String>>
|
||||
get() = _apiResultError
|
||||
val firstState = state.copy(
|
||||
keysBackupState = session.getKeysBackupService().state,
|
||||
keysBackupVersion = session.getKeysBackupService().keysBackupVersion
|
||||
)
|
||||
|
||||
var loadingEvent: MutableLiveData<WaitingViewData> = MutableLiveData()
|
||||
|
||||
fun initSession(session: Session) {
|
||||
keyBackupState.value = session.getKeysBackupService().state
|
||||
if (this.session == null) {
|
||||
this.session = session
|
||||
session.getKeysBackupService().addListener(this)
|
||||
return KeysBackupSettingsViewModel(firstState, session)
|
||||
}
|
||||
}
|
||||
|
||||
fun getKeysBackupTrust(versionResult: KeysVersionResult) {
|
||||
val keysBackup = session?.getKeysBackupService()
|
||||
keysBackup?.getKeysBackupTrust(versionResult, object : MatrixCallback<KeysBackupVersionTrust> {
|
||||
private var keysBackupService: KeysBackupService = session.getKeysBackupService()
|
||||
|
||||
init {
|
||||
keysBackupService.addListener(this)
|
||||
|
||||
getKeysBackupTrust()
|
||||
}
|
||||
|
||||
fun init() {
|
||||
keysBackupService.forceUsingLastVersion(object : MatrixCallback<Boolean> {})
|
||||
}
|
||||
|
||||
fun getKeysBackupTrust() = withState { state ->
|
||||
val versionResult = keysBackupService.keysBackupVersion
|
||||
|
||||
if (state.keysBackupVersionTrust is Uninitialized && versionResult != null) {
|
||||
setState {
|
||||
copy(
|
||||
keysBackupVersionTrust = Loading(),
|
||||
deleteBackupRequest = Uninitialized
|
||||
)
|
||||
}
|
||||
|
||||
keysBackupService
|
||||
.getKeysBackupTrust(versionResult, object : MatrixCallback<KeysBackupVersionTrust> {
|
||||
override fun onSuccess(data: KeysBackupVersionTrust) {
|
||||
keyVersionTrust.value = data
|
||||
setState {
|
||||
copy(
|
||||
keysBackupVersionTrust = Success(data)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
keysBackupVersionTrust = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
session?.getKeysBackupService()?.removeListener(this)
|
||||
keysBackupService.removeListener(this)
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
keyBackupState.value = newState
|
||||
setState {
|
||||
copy(
|
||||
keysBackupState = newState,
|
||||
keysBackupVersion = keysBackupService.keysBackupVersion
|
||||
)
|
||||
}
|
||||
|
||||
getKeysBackupTrust()
|
||||
}
|
||||
|
||||
fun deleteCurrentBackup(context: Context) {
|
||||
session?.getKeysBackupService()?.run {
|
||||
loadingEvent.value = WaitingViewData(context.getString(R.string.keys_backup_settings_deleting_backup))
|
||||
if (currentBackupVersion != null) {
|
||||
deleteBackup(currentBackupVersion!!, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(info: Unit) {
|
||||
//mmmm if state is stil unknown/checking..
|
||||
loadingEvent.value = null
|
||||
val keysBackupService = keysBackupService
|
||||
|
||||
if (keysBackupService.currentBackupVersion != null) {
|
||||
setState {
|
||||
copy(
|
||||
deleteBackupRequest = Loading()
|
||||
)
|
||||
}
|
||||
|
||||
keysBackupService.deleteBackup(keysBackupService.currentBackupVersion!!, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
setState {
|
||||
copy(
|
||||
keysBackupVersion = null,
|
||||
keysBackupVersionTrust = Uninitialized,
|
||||
// We do not care about the success data
|
||||
deleteBackupRequest = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
loadingEvent.value = null
|
||||
_apiResultError.value = LiveEvent(context.getString(R.string.keys_backup_get_version_error, failure.localizedMessage))
|
||||
setState {
|
||||
copy(
|
||||
deleteBackupRequest = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -117,6 +117,10 @@ class KeysBackupSetupStep3Fragment : VectorBaseFragment() {
|
||||
.chunked(4)
|
||||
.joinToString(" ")
|
||||
}
|
||||
|
||||
it.setOnClickListener {
|
||||
copyToClipboard(activity!!, recoveryKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,7 +155,7 @@ class KeysBackupSetupStep3Fragment : VectorBaseFragment() {
|
||||
fun exportRecoveryKeyToFile(it: String) {
|
||||
val stream = ByteArrayInputStream(it.toByteArray())
|
||||
|
||||
TODO()
|
||||
vectorBaseActivity.notImplemented("export recovery key to file")
|
||||
/*
|
||||
val url = viewModel.session.mediaCache.saveMedia(stream, "recovery-key" + System.currentTimeMillis() + ".txt", "text/plain")
|
||||
stream.close()
|
||||
|
@ -46,8 +46,7 @@
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/VectorTextInputLayout"
|
||||
|
@ -66,6 +66,7 @@
|
||||
android:layout_height="@dimen/layout_touch_size"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:scaleType="center"
|
||||
android:layout_marginTop="8dp"
|
||||
android:src="@drawable/ic_eye_black"
|
||||
android:tint="?colorAccent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/keys_backup_settings_recycler_view"
|
||||
android:id="@+id/keysBackupSettingsRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
|
@ -51,6 +51,7 @@
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:maxWidth="@dimen/button_max_width"
|
||||
android:minWidth="200dp"
|
||||
android:padding="8dp"
|
||||
android:text="@string/keys_backup_setup"
|
||||
|
@ -72,6 +72,7 @@
|
||||
android:id="@+id/keys_backup_view_show_password"
|
||||
android:layout_width="@dimen/layout_touch_size"
|
||||
android:layout_height="@dimen/layout_touch_size"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_eye_black"
|
||||
@ -145,6 +146,8 @@
|
||||
style="@style/VectorButtonStyleFlat"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="@dimen/layout_vertical_margin"
|
||||
android:maxWidth="@dimen/button_max_width"
|
||||
android:text="@string/keys_backup_setup_step2_skip_button_title"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -46,7 +46,7 @@
|
||||
android:layout_marginRight="@dimen/layout_horizontal_margin"
|
||||
android:text="@string/keys_backup_setup_step3_text_line1"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
@ -80,6 +80,7 @@
|
||||
android:id="@+id/keys_backup_setup_step3_copy_button"
|
||||
style="@style/VectorButtonStyleFlat"
|
||||
android:layout_gravity="center"
|
||||
android:maxWidth="@dimen/button_max_width"
|
||||
android:text="@string/keys_backup_setup_step3_copy_button_title" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
|
@ -2,6 +2,7 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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/item_generic_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/colorBackground"
|
||||
@ -9,22 +10,26 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_generic_title_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/item_generic_description_text"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_generic_barrier"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Item Title"
|
||||
tools:textSize="14sp" />
|
||||
tools:textSize="14sp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_generic_description_text"
|
||||
@ -32,10 +37,10 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/item_generic_action_button"
|
||||
@ -49,6 +54,7 @@
|
||||
android:id="@+id/item_generic_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="start"
|
||||
app:constraint_referenced_ids="item_generic_accessory_image,item_generic_progress_bar"
|
||||
tools:ignore="MissingConstraints" />
|
||||
|
||||
@ -76,17 +82,20 @@
|
||||
app:layout_constraintTop_toTopOf="@+id/item_generic_title_text"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- Set a maw width because the text can be long -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/item_generic_action_button"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:padding="8dp"
|
||||
android:maxWidth="@dimen/button_max_width"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/item_generic_description_text"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_generic_barrier"
|
||||
app:layout_constraintTop_toBottomOf="@+id/item_generic_description_text"
|
||||
tools:text="@string/settings_troubleshoot_test_device_settings_quickfix"
|
||||
tools:visibility="visible" />
|
||||
|
@ -5,15 +5,13 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/keys_backup_settings_footer_button1"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:minWidth="200dp"
|
||||
android:layout_width="@dimen/button_max_width"
|
||||
android:visibility="gone"
|
||||
tools:text="@string/keys_backup_settings_restore_backup_button"
|
||||
tools:visibility="visible" />
|
||||
@ -21,10 +19,8 @@
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/keys_backup_settings_footer_button2"
|
||||
style="@style/VectorButtonStyle"
|
||||
android:layout_width="@dimen/button_max_width"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:minWidth="200dp"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="@color/vector_warning_color"
|
||||
tools:text="@string/keys_backup_settings_delete_backup_button"
|
||||
|
@ -30,4 +30,7 @@
|
||||
|
||||
<dimen name="item_form_min_height">76dp</dimen>
|
||||
|
||||
<!-- Max width for some buttons -->
|
||||
<dimen name="button_max_width">280dp</dimen>
|
||||
|
||||
</resources>
|
@ -51,4 +51,7 @@
|
||||
<string name="create_room_directory_title">"Room Directory"</string>
|
||||
<string name="create_room_directory_description">"Publish this room in the room directory"</string>
|
||||
|
||||
<string name="keys_backup_unable_to_get_trust_info">"An error occurred getting trust info"</string>
|
||||
<string name="keys_backup_unable_to_get_keys_backup_data">"An error occurred getting keys backup data"</string>
|
||||
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user