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.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
import androidx.core.provider.FontRequest
|
import androidx.core.provider.FontRequest
|
||||||
import androidx.core.provider.FontsContractCompat
|
import androidx.core.provider.FontsContractCompat
|
||||||
import android.content.res.Configuration
|
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
@ -33,6 +33,7 @@ import com.jakewharton.threetenabp.AndroidThreeTen
|
|||||||
import im.vector.matrix.android.api.Matrix
|
import im.vector.matrix.android.api.Matrix
|
||||||
import im.vector.riotredesign.core.di.AppModule
|
import im.vector.riotredesign.core.di.AppModule
|
||||||
import im.vector.riotredesign.features.configuration.VectorConfiguration
|
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.home.HomeModule
|
||||||
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
|
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
|
||||||
import im.vector.riotredesign.features.rageshake.VectorFileLogger
|
import im.vector.riotredesign.features.rageshake.VectorFileLogger
|
||||||
@ -73,7 +74,8 @@ class VectorApplication : Application() {
|
|||||||
val appModule = AppModule(applicationContext).definition
|
val appModule = AppModule(applicationContext).definition
|
||||||
val homeModule = HomeModule().definition
|
val homeModule = HomeModule().definition
|
||||||
val roomDirectoryModule = RoomDirectoryModule().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)
|
Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION)
|
||||||
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks())
|
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks())
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import android.widget.TextView
|
|||||||
import androidx.core.view.isVisible
|
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) {
|
fun TextView.setTextOrHide(newText: String?, hideWhenBlank: Boolean = true) {
|
||||||
if (newText == null
|
if (newText == null
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.riotredesign.core.resources
|
|||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
|
import androidx.annotation.PluralsRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
|
||||||
class StringProvider(private val resources: Resources) {
|
class StringProvider(private val resources: Resources) {
|
||||||
@ -51,5 +52,9 @@ class StringProvider(private val resources: Resources) {
|
|||||||
return resources.getString(resId, *formatArgs)
|
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.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.Observer
|
import com.airbnb.mvrx.Fail
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import com.airbnb.mvrx.Loading
|
||||||
import im.vector.fragments.keysbackup.settings.KeysBackupSettingsFragment
|
import com.airbnb.mvrx.viewModel
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.platform.SimpleFragmentActivity
|
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() {
|
class KeysBackupManageActivity : SimpleFragmentActivity() {
|
||||||
@ -31,46 +34,47 @@ class KeysBackupManageActivity : SimpleFragmentActivity() {
|
|||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun intent(context: Context): Intent {
|
fun intent(context: Context): Intent {
|
||||||
val intent = Intent(context, KeysBackupManageActivity::class.java)
|
return Intent(context, KeysBackupManageActivity::class.java)
|
||||||
return intent
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTitleRes() = R.string.encryption_message_recovery
|
override fun getTitleRes() = R.string.encryption_message_recovery
|
||||||
|
|
||||||
|
private val viewModel: KeysBackupSettingsViewModel by viewModel()
|
||||||
private lateinit var viewModel: KeysBackupSettingsViewModel
|
|
||||||
|
|
||||||
override fun initUiAndData() {
|
override fun initUiAndData() {
|
||||||
super.initUiAndData()
|
super.initUiAndData()
|
||||||
viewModel = ViewModelProviders.of(this).get(KeysBackupSettingsViewModel::class.java)
|
|
||||||
viewModel.initSession(session)
|
|
||||||
|
|
||||||
|
bindScope(getOrCreateScope(KeysBackupModule.KEYS_BACKUP_SCOPE))
|
||||||
|
|
||||||
if (supportFragmentManager.fragments.isEmpty()) {
|
if (supportFragmentManager.fragments.isEmpty()) {
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.container, KeysBackupSettingsFragment.newInstance())
|
.replace(R.id.container, KeysBackupSettingsFragment.newInstance())
|
||||||
.commitNow()
|
.commitNow()
|
||||||
|
|
||||||
session.getKeysBackupService()
|
viewModel.init()
|
||||||
.forceUsingLastVersion(object : MatrixCallback<Boolean> {})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.loadingEvent.observe(this, Observer {
|
// Observe the deletion of keys backup
|
||||||
updateWaitingView(it)
|
viewModel.selectSubscribe(this, KeysBackupSettingViewState::deleteBackupRequest) { asyncDelete ->
|
||||||
})
|
when (asyncDelete) {
|
||||||
|
is Fail -> {
|
||||||
|
updateWaitingView(null)
|
||||||
|
|
||||||
|
AlertDialog.Builder(this)
|
||||||
viewModel.apiResultError.observe(this, Observer { uxStateEvent ->
|
.setTitle(R.string.unknown_error)
|
||||||
uxStateEvent?.getContentIfNotHandled()?.let {
|
.setMessage(getString(R.string.keys_backup_get_version_error, asyncDelete.error.localizedMessage))
|
||||||
AlertDialog.Builder(this)
|
.setCancelable(false)
|
||||||
.setTitle(R.string.unknown_error)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.setMessage(it)
|
.show()
|
||||||
.setCancelable(false)
|
}
|
||||||
.setPositiveButton(R.string.ok, null)
|
is Loading -> {
|
||||||
.show()
|
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package im.vector.fragments.keysbackup.settings
|
package im.vector.riotredesign.features.crypto.keysbackup.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.Observer
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import com.airbnb.mvrx.withState
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import butterknife.BindView
|
|
||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.platform.VectorBaseFragment
|
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.restore.KeysBackupRestoreActivity
|
||||||
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel
|
|
||||||
import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
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(),
|
class KeysBackupSettingsFragment : VectorBaseFragment(),
|
||||||
KeysBackupSettingsRecyclerViewAdapter.AdapterListener {
|
KeysBackupSettingsRecyclerViewController.Listener {
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = KeysBackupSettingsFragment()
|
fun newInstance() = KeysBackupSettingsFragment()
|
||||||
@ -41,63 +39,26 @@ class KeysBackupSettingsFragment : VectorBaseFragment(),
|
|||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_keys_backup_settings
|
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)
|
private val viewModel: KeysBackupSettingsViewModel by activityViewModel()
|
||||||
lateinit var recyclerView: RecyclerView
|
|
||||||
|
|
||||||
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val layoutManager = LinearLayoutManager(context)
|
keysBackupSettingsRecyclerView.setController(keysBackupSettingsRecyclerViewController)
|
||||||
recyclerView.layoutManager = layoutManager
|
|
||||||
|
|
||||||
recyclerViewAdapter = KeysBackupSettingsRecyclerViewAdapter(activity!!)
|
keysBackupSettingsRecyclerViewController.listener = this
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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() {
|
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
|
package im.vector.riotredesign.features.crypto.keysbackup.settings
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.LiveData
|
import com.airbnb.mvrx.*
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.Session
|
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.KeysBackupService
|
||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
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.KeysBackupVersionTrust
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
import im.vector.riotredesign.core.platform.VectorViewModel
|
||||||
import im.vector.riotredesign.R
|
import org.koin.android.ext.android.get
|
||||||
import im.vector.riotredesign.core.platform.WaitingViewData
|
|
||||||
import im.vector.riotredesign.core.utils.LiveEvent
|
|
||||||
|
|
||||||
|
|
||||||
class KeysBackupSettingsViewModel : ViewModel(),
|
class KeysBackupSettingsViewModel(initialState: KeysBackupSettingViewState,
|
||||||
|
session: Session) : VectorViewModel<KeysBackupSettingViewState>(initialState),
|
||||||
KeysBackupService.KeysBackupStateListener {
|
KeysBackupService.KeysBackupStateListener {
|
||||||
|
|
||||||
var session: Session? = null
|
companion object : MvRxViewModelFactory<KeysBackupSettingsViewModel, KeysBackupSettingViewState> {
|
||||||
|
|
||||||
var keyVersionTrust: MutableLiveData<KeysBackupVersionTrust> = MutableLiveData()
|
@JvmStatic
|
||||||
var keyBackupState: MutableLiveData<KeysBackupState> = MutableLiveData()
|
override fun create(viewModelContext: ViewModelContext, state: KeysBackupSettingViewState): KeysBackupSettingsViewModel? {
|
||||||
|
val session = viewModelContext.activity.get<Session>()
|
||||||
|
|
||||||
private var _apiResultError: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
val firstState = state.copy(
|
||||||
val apiResultError: LiveData<LiveEvent<String>>
|
keysBackupState = session.getKeysBackupService().state,
|
||||||
get() = _apiResultError
|
keysBackupVersion = session.getKeysBackupService().keysBackupVersion
|
||||||
|
)
|
||||||
|
|
||||||
var loadingEvent: MutableLiveData<WaitingViewData> = MutableLiveData()
|
return KeysBackupSettingsViewModel(firstState, session)
|
||||||
|
|
||||||
fun initSession(session: Session) {
|
|
||||||
keyBackupState.value = session.getKeysBackupService().state
|
|
||||||
if (this.session == null) {
|
|
||||||
this.session = session
|
|
||||||
session.getKeysBackupService().addListener(this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getKeysBackupTrust(versionResult: KeysVersionResult) {
|
private var keysBackupService: KeysBackupService = session.getKeysBackupService()
|
||||||
val keysBackup = session?.getKeysBackupService()
|
|
||||||
keysBackup?.getKeysBackupTrust(versionResult, object : MatrixCallback<KeysBackupVersionTrust> {
|
init {
|
||||||
override fun onSuccess(data: KeysBackupVersionTrust) {
|
keysBackupService.addListener(this)
|
||||||
keyVersionTrust.value = data
|
|
||||||
|
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) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
keysBackupVersionTrust = Success(data)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
keysBackupVersionTrust = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
session?.getKeysBackupService()?.removeListener(this)
|
keysBackupService.removeListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStateChange(newState: KeysBackupState) {
|
override fun onStateChange(newState: KeysBackupState) {
|
||||||
keyBackupState.value = newState
|
setState {
|
||||||
|
copy(
|
||||||
|
keysBackupState = newState,
|
||||||
|
keysBackupVersion = keysBackupService.keysBackupVersion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeysBackupTrust()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteCurrentBackup(context: Context) {
|
fun deleteCurrentBackup(context: Context) {
|
||||||
session?.getKeysBackupService()?.run {
|
val keysBackupService = keysBackupService
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
if (keysBackupService.currentBackupVersion != null) {
|
||||||
loadingEvent.value = null
|
setState {
|
||||||
_apiResultError.value = LiveEvent(context.getString(R.string.keys_backup_get_version_error, failure.localizedMessage))
|
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) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
deleteBackupRequest = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -117,6 +117,10 @@ class KeysBackupSetupStep3Fragment : VectorBaseFragment() {
|
|||||||
.chunked(4)
|
.chunked(4)
|
||||||
.joinToString(" ")
|
.joinToString(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it.setOnClickListener {
|
||||||
|
copyToClipboard(activity!!, recoveryKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +155,7 @@ class KeysBackupSetupStep3Fragment : VectorBaseFragment() {
|
|||||||
fun exportRecoveryKeyToFile(it: String) {
|
fun exportRecoveryKeyToFile(it: String) {
|
||||||
val stream = ByteArrayInputStream(it.toByteArray())
|
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")
|
val url = viewModel.session.mediaCache.saveMedia(stream, "recovery-key" + System.currentTimeMillis() + ".txt", "text/plain")
|
||||||
stream.close()
|
stream.close()
|
||||||
|
@ -46,8 +46,7 @@
|
|||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
style="@style/VectorTextInputLayout"
|
style="@style/VectorTextInputLayout"
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
android:layout_height="@dimen/layout_touch_size"
|
android:layout_height="@dimen/layout_touch_size"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
android:src="@drawable/ic_eye_black"
|
android:src="@drawable/ic_eye_black"
|
||||||
android:tint="?colorAccent"
|
android:tint="?colorAccent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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"
|
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_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
style="@style/VectorButtonStyle"
|
style="@style/VectorButtonStyle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
|
android:maxWidth="@dimen/button_max_width"
|
||||||
android:minWidth="200dp"
|
android:minWidth="200dp"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:text="@string/keys_backup_setup"
|
android:text="@string/keys_backup_setup"
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
android:id="@+id/keys_backup_view_show_password"
|
android:id="@+id/keys_backup_view_show_password"
|
||||||
android:layout_width="@dimen/layout_touch_size"
|
android:layout_width="@dimen/layout_touch_size"
|
||||||
android:layout_height="@dimen/layout_touch_size"
|
android:layout_height="@dimen/layout_touch_size"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
android:src="@drawable/ic_eye_black"
|
android:src="@drawable/ic_eye_black"
|
||||||
@ -145,6 +146,8 @@
|
|||||||
style="@style/VectorButtonStyleFlat"
|
style="@style/VectorButtonStyleFlat"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="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"
|
android:text="@string/keys_backup_setup_step2_skip_button_title"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
android:layout_marginRight="@dimen/layout_horizontal_margin"
|
android:layout_marginRight="@dimen/layout_horizontal_margin"
|
||||||
android:text="@string/keys_backup_setup_step3_text_line1"
|
android:text="@string/keys_backup_setup_step3_text_line1"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textColor="?riotx_text_secondary"
|
android:textColor="?riotx_text_primary"
|
||||||
android:textSize="15sp" />
|
android:textSize="15sp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@ -80,6 +80,7 @@
|
|||||||
android:id="@+id/keys_backup_setup_step3_copy_button"
|
android:id="@+id/keys_backup_setup_step3_copy_button"
|
||||||
style="@style/VectorButtonStyleFlat"
|
style="@style/VectorButtonStyleFlat"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:maxWidth="@dimen/button_max_width"
|
||||||
android:text="@string/keys_backup_setup_step3_copy_button_title" />
|
android:text="@string/keys_backup_setup_step3_copy_button_title" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/item_generic_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?android:attr/colorBackground"
|
android:background="?android:attr/colorBackground"
|
||||||
@ -9,22 +10,26 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/item_generic_title_text"
|
android:id="@+id/item_generic_title_text"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginLeft="16dp"
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginRight="16dp"
|
android:layout_marginRight="16dp"
|
||||||
android:paddingTop="4dp"
|
android:layout_marginBottom="4dp"
|
||||||
android:paddingBottom="4dp"
|
|
||||||
android:textColor="?riotx_text_primary"
|
android:textColor="?riotx_text_primary"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/item_generic_description_text"
|
app:layout_constraintBottom_toTopOf="@+id/item_generic_description_text"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/item_generic_barrier"
|
app:layout_constraintEnd_toStartOf="@+id/item_generic_barrier"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="Item Title"
|
tools:text="Item Title"
|
||||||
tools:textSize="14sp" />
|
tools:textSize="14sp"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/item_generic_description_text"
|
android:id="@+id/item_generic_description_text"
|
||||||
@ -32,10 +37,10 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginLeft="16dp"
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginRight="16dp"
|
android:layout_marginRight="16dp"
|
||||||
android:paddingTop="4dp"
|
android:layout_marginBottom="4dp"
|
||||||
android:paddingBottom="4dp"
|
|
||||||
android:textColor="?riotx_text_secondary"
|
android:textColor="?riotx_text_secondary"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toTopOf="@id/item_generic_action_button"
|
app:layout_constraintBottom_toTopOf="@id/item_generic_action_button"
|
||||||
@ -49,6 +54,7 @@
|
|||||||
android:id="@+id/item_generic_barrier"
|
android:id="@+id/item_generic_barrier"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="start"
|
||||||
app:constraint_referenced_ids="item_generic_accessory_image,item_generic_progress_bar"
|
app:constraint_referenced_ids="item_generic_accessory_image,item_generic_progress_bar"
|
||||||
tools:ignore="MissingConstraints" />
|
tools:ignore="MissingConstraints" />
|
||||||
|
|
||||||
@ -76,17 +82,20 @@
|
|||||||
app:layout_constraintTop_toTopOf="@+id/item_generic_title_text"
|
app:layout_constraintTop_toTopOf="@+id/item_generic_title_text"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<!-- Set a maw width because the text can be long -->
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/item_generic_action_button"
|
android:id="@+id/item_generic_action_button"
|
||||||
style="@style/VectorButtonStyle"
|
style="@style/VectorButtonStyle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:padding="8dp"
|
android:maxWidth="@dimen/button_max_width"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
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"
|
app:layout_constraintTop_toBottomOf="@+id/item_generic_description_text"
|
||||||
tools:text="@string/settings_troubleshoot_test_device_settings_quickfix"
|
tools:text="@string/settings_troubleshoot_test_device_settings_quickfix"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
@ -5,15 +5,13 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/keys_backup_settings_footer_button1"
|
android:id="@+id/keys_backup_settings_footer_button1"
|
||||||
style="@style/VectorButtonStyle"
|
style="@style/VectorButtonStyle"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_width="@dimen/button_max_width"
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:minWidth="200dp"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:text="@string/keys_backup_settings_restore_backup_button"
|
tools:text="@string/keys_backup_settings_restore_backup_button"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
@ -21,10 +19,8 @@
|
|||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/keys_backup_settings_footer_button2"
|
android:id="@+id/keys_backup_settings_footer_button2"
|
||||||
style="@style/VectorButtonStyle"
|
style="@style/VectorButtonStyle"
|
||||||
|
android:layout_width="@dimen/button_max_width"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:minWidth="200dp"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:backgroundTint="@color/vector_warning_color"
|
app:backgroundTint="@color/vector_warning_color"
|
||||||
tools:text="@string/keys_backup_settings_delete_backup_button"
|
tools:text="@string/keys_backup_settings_delete_backup_button"
|
||||||
|
@ -30,4 +30,7 @@
|
|||||||
|
|
||||||
<dimen name="item_form_min_height">76dp</dimen>
|
<dimen name="item_form_min_height">76dp</dimen>
|
||||||
|
|
||||||
|
<!-- Max width for some buttons -->
|
||||||
|
<dimen name="button_max_width">280dp</dimen>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
@ -51,4 +51,7 @@
|
|||||||
<string name="create_room_directory_title">"Room Directory"</string>
|
<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="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>
|
</resources>
|
Loading…
Reference in New Issue
Block a user