Keys backup: migrate settings to Epoxy and MvRx

This commit is contained in:
Benoit Marty 2019-06-12 13:09:32 +02:00
parent 53dd9c3427
commit b47ef9220e
25 changed files with 672 additions and 539 deletions

View File

@ -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())



View File

@ -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

View File

@ -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)
}


} }

View File

@ -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)
}
}

View File

@ -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()
}
}
}

View File

@ -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
}
}

View File

@ -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())
}

}
}

View File

@ -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)
}
} }
}) }

} }
} }

View File

@ -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)
}

}

View File

@ -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

View File

@ -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()
}
} }

View File

@ -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()
}

}

View File

@ -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()
}
}

View File

@ -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)
)
}
}
})
} }
} }
} }

View File

@ -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()

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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" />

View File

@ -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"

View File

@ -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>

View File

@ -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>