Update My device list + action to verify

This commit is contained in:
Valere 2020-01-23 13:57:17 +01:00
parent a0aa1f34d3
commit 1276d1f39d
19 changed files with 692 additions and 250 deletions

View File

@ -184,7 +184,17 @@ internal class DefaultCryptoService @Inject constructor(
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
setDeviceNameTask
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
this.callback = callback
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
//bg refresh of crypto device
downloadKeys(listOf(credentials.userId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {})
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
}
}
.executeBy(taskExecutor)
}

View File

@ -130,6 +130,10 @@ internal class DefaultCrossSigningService @Inject constructor(
listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() }
}
protected fun finalize() {
release()
}
/**
* - Make 3 key pairs (MSK, USK, SSK)
* - Save the private keys with proper security

View File

@ -52,6 +52,7 @@ import im.vector.riotx.features.reactions.widget.ReactionButton
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
import im.vector.riotx.features.settings.VectorSettingsActivity
import im.vector.riotx.features.settings.devices.DeviceVerificationInfoBottomSheet
import im.vector.riotx.features.share.IncomingShareActivity
import im.vector.riotx.features.signout.soft.SoftLogoutActivity
import im.vector.riotx.features.ui.UiStateRepository
@ -142,6 +143,8 @@ interface ScreenComponent {
fun inject(activity: DebugMenuActivity)
fun inject(deviceVerificationInfoBottomSheet: DeviceVerificationInfoBottomSheet)
@Component.Factory
interface Factory {
fun create(vectorComponent: VectorComponent,

View File

@ -60,6 +60,10 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
@DrawableRes
var endIconResourceId: Int = -1
@EpoxyAttribute
@DrawableRes
var titleIconResourceId: Int = -1
@EpoxyAttribute
var hasIndeterminateProcess = false
@ -72,6 +76,13 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
override fun bind(holder: Holder) {
holder.titleText.setTextOrHide(title)
if (titleIconResourceId != -1) {
holder.titleIcon.setImageResource(titleIconResourceId)
holder.titleIcon.isVisible = true
} else {
holder.titleIcon.isVisible = false
}
when (style) {
STYLE.BIG_TEXT -> holder.titleText.textSize = 18f
STYLE.NORMAL_TEXT -> holder.titleText.textSize = 14f
@ -104,7 +115,7 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
class Holder : VectorEpoxyHolder() {
val root by bind<View>(R.id.item_generic_root)
val titleIcon by bind<ImageView>(R.id.item_generic_title_image)
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)

View File

@ -66,13 +66,11 @@ class VectorPreferences @Inject constructor(private val context: Context) {
const val SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY"
const val SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY = "SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY"
const val SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY = "SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_CROSS_SIGNING_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_CROSS_SIGNING_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_EXPORT_E2E_ROOM_KEYS_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_EXPORT_E2E_ROOM_KEYS_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY"
const val SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY = "SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY"
const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY"

View File

@ -1,5 +1,6 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright 2020 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.
@ -28,10 +29,10 @@ import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreference
import com.google.android.material.textfield.TextInputEditText
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.extensions.getFingerprintHumanReadable
import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.riotx.R
import im.vector.riotx.core.dialogs.ExportKeysDialog
import im.vector.riotx.core.intent.ExternalIntentData
@ -43,7 +44,6 @@ import im.vector.riotx.core.utils.PERMISSIONS_FOR_WRITING_FILES
import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_EXPORT_KEYS
import im.vector.riotx.core.utils.allGranted
import im.vector.riotx.core.utils.checkPermissions
import im.vector.riotx.core.utils.copyToClipboard
import im.vector.riotx.core.utils.openFileSelection
import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.crypto.keys.KeysExporter
@ -80,14 +80,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
findPreference<VectorPreference>(VectorPreferences.SETTINGS_ENCRYPTION_CROSS_SIGNING_PREFERENCE_KEY)!!
}
private val cryptoInfoDeviceNamePreference by lazy {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY)!!
}
private val cryptoInfoDeviceIdPreference by lazy {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY)!!
}
private val manageBackupPref by lazy {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY)!!
}
@ -100,9 +92,10 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
findPreference<VectorPreference>(VectorPreferences.SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY)!!
}
private val cryptoInfoTextPreference by lazy {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY)!!
private val showDeviceListPref by lazy {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY)!!
}
// encrypt to unverified devices
private val sendToUnverifiedDevicesPref by lazy {
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY)!!
@ -112,6 +105,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
super.onResume()
// My device name may have been updated
refreshMyDevice()
mCryptographyCategory.isVisible = vectorPreferences.developerMode()
}
override fun bindPref() {
@ -137,13 +131,13 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
if (vectorPreferences.developerMode()) {
val crossSigningKeys = session.getCrossSigningService().getMyCrossSigningKeys()
val xSigningIsEnableInAccount = crossSigningKeys != null
val xSigningCaseAreTrusted = session.getCrossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeysAreTrusted = session.getCrossSigningService().checkUserTrust(session.myUserId).isVerified()
val xSigningKeyCanSign = session.getCrossSigningService().canCrossSign()
if (xSigningKeyCanSign) {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
} else if (xSigningCaseAreTrusted) {
} else if (xSigningKeysAreTrusted) {
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_warning)
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
} else if (xSigningIsEnableInAccount) {
@ -361,53 +355,55 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
*
* @param aMyDeviceInfo the device info
*/
private fun refreshCryptographyPreference(aMyDeviceInfo: DeviceInfo?) {
val userId = session.myUserId
val deviceId = session.sessionParams.credentials.deviceId
private fun refreshCryptographyPreference(devices: List<DeviceInfo>) {
showDeviceListPref.isEnabled = devices.size > 0
showDeviceListPref.summary = resources.getQuantityString(R.plurals.settings_active_sessions_count, devices.size, devices.size)
// val userId = session.myUserId
// val deviceId = session.sessionParams.credentials.deviceId
// device name
if (null != aMyDeviceInfo) {
cryptoInfoDeviceNamePreference.summary = aMyDeviceInfo.displayName
cryptoInfoDeviceNamePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
// TODO device can be rename only from the device list screen for the moment
// displayDeviceRenameDialog(aMyDeviceInfo)
true
}
cryptoInfoDeviceNamePreference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener {
override fun onPreferenceLongClick(preference: Preference): Boolean {
activity?.let { copyToClipboard(it, aMyDeviceInfo.displayName!!) }
return true
}
}
}
// crypto section: device ID
if (!deviceId.isNullOrEmpty()) {
cryptoInfoDeviceIdPreference.summary = deviceId
cryptoInfoDeviceIdPreference.setOnPreferenceClickListener {
activity?.let { copyToClipboard(it, deviceId) }
true
}
}
// crypto section: device key (fingerprint)
if (!deviceId.isNullOrEmpty() && userId.isNotEmpty()) {
val deviceInfo = session.getDeviceInfo(userId, deviceId)
if (null != deviceInfo && !deviceInfo.fingerprint().isNullOrEmpty()) {
cryptoInfoTextPreference.summary = deviceInfo.getFingerprintHumanReadable()
cryptoInfoTextPreference.setOnPreferenceClickListener {
deviceInfo.fingerprint()?.let {
copyToClipboard(requireActivity(), it)
}
true
}
}
}
// if (null != aMyDeviceInfo) {
// cryptoInfoDeviceNamePreference.summary = aMyDeviceInfo.displayName
//
// cryptoInfoDeviceNamePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
// // TODO device can be rename only from the device list screen for the moment
// // displayDeviceRenameDialog(aMyDeviceInfo)
// true
// }
//
// cryptoInfoDeviceNamePreference.onPreferenceLongClickListener = object : VectorPreference.OnPreferenceLongClickListener {
// override fun onPreferenceLongClick(preference: Preference): Boolean {
// activity?.let { copyToClipboard(it, aMyDeviceInfo.displayName!!) }
// return true
// }
// }
// }
//
// // crypto section: device ID
// if (!deviceId.isNullOrEmpty()) {
// cryptoInfoDeviceIdPreference.summary = deviceId
//
// cryptoInfoDeviceIdPreference.setOnPreferenceClickListener {
// activity?.let { copyToClipboard(it, deviceId) }
// true
// }
// }
//
// // crypto section: device key (fingerprint)
// if (!deviceId.isNullOrEmpty() && userId.isNotEmpty()) {
// val deviceInfo = session.getDeviceInfo(userId, deviceId)
//
// if (null != deviceInfo && !deviceInfo.fingerprint().isNullOrEmpty()) {
// cryptoInfoTextPreference.summary = deviceInfo.getFingerprintHumanReadable()
//
// cryptoInfoTextPreference.setOnPreferenceClickListener {
// deviceInfo.fingerprint()?.let {
// copyToClipboard(requireActivity(), it)
// }
// true
// }
// }
// }
sendToUnverifiedDevicesPref.isChecked = false
@ -426,18 +422,15 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
private fun refreshMyDevice() {
// TODO Move to a ViewModel...
session.sessionParams.credentials.deviceId?.let {
session.getDeviceInfo(it, object : MatrixCallback<DeviceInfo> {
override fun onFailure(failure: Throwable) {
// Ignore for this time?...
}
session.getDevicesList(object : MatrixCallback<DevicesListResponse> {
override fun onSuccess(data: DevicesListResponse) {
refreshCryptographyPreference(data.devices ?: emptyList())
}
override fun onSuccess(data: DeviceInfo) {
mMyDeviceInfo = data
refreshCryptographyPreference(data)
}
})
}
override fun onFailure(failure: Throwable) {
refreshCryptographyPreference(emptyList())
}
})
}
// ==============================================================================================================

View File

@ -19,7 +19,10 @@ package im.vector.riotx.features.settings.devices
import android.graphics.Typeface
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
@ -29,7 +32,8 @@ import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import java.util.Date
import java.util.Locale
/**
* A list item for Device.
@ -43,67 +47,82 @@ abstract class DeviceItem : VectorEpoxyModel<DeviceItem.Holder>() {
@EpoxyAttribute
var currentDevice = false
@EpoxyAttribute
var buttonsVisible = false
@EpoxyAttribute
var itemClickAction: (() -> Unit)? = null
@EpoxyAttribute
var renameClickAction: (() -> Unit)? = null
var detailedMode = false
@EpoxyAttribute
var deleteClickAction: (() -> Unit)? = null
var trusted : Boolean? = false
override fun bind(holder: Holder) {
holder.root.setOnClickListener { itemClickAction?.invoke() }
holder.displayNameText.text = deviceInfo.displayName ?: ""
holder.deviceIdText.text = deviceInfo.deviceId ?: ""
if (trusted != null) {
holder.trustIcon.setImageDrawable(
ContextCompat.getDrawable(
holder.view.context,
if (trusted!!) R.drawable.ic_shield_trusted else R.drawable.ic_shield_warning
)
)
holder.trustIcon.isInvisible = false
} else {
holder.trustIcon.isInvisible = true
}
val lastSeenIp = deviceInfo.lastSeenIp?.takeIf { ip -> ip.isNotBlank() } ?: "-"
val lastSeenTime = deviceInfo.lastSeenTs?.let { ts ->
val dateFormatTime = SimpleDateFormat("HH:mm:ss", Locale.ROOT)
val date = Date(ts)
val time = dateFormatTime.format(date)
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault())
dateFormat.format(date) + ", " + time
} ?: "-"
holder.deviceLastSeenText.text = holder.root.context.getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
listOf(
val detailedModeLabels = listOf(
holder.displayNameLabelText,
holder.displayNameText,
holder.deviceIdLabelText,
holder.deviceIdText,
holder.deviceLastSeenLabelText,
holder.deviceLastSeenText
).map {
it.setTypeface(null, if (currentDevice) Typeface.BOLD else Typeface.NORMAL)
)
if (detailedMode) {
holder.summaryLabelText.isVisible = false
holder.displayNameText.text = deviceInfo.displayName ?: ""
holder.deviceIdText.text = deviceInfo.deviceId ?: ""
val lastSeenIp = deviceInfo.lastSeenIp?.takeIf { ip -> ip.isNotBlank() } ?: "-"
val lastSeenTime = deviceInfo.lastSeenTs?.let { ts ->
val dateFormatTime = SimpleDateFormat("HH:mm:ss", Locale.ROOT)
val date = Date(ts)
val time = dateFormatTime.format(date)
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault())
dateFormat.format(date) + ", " + time
} ?: "-"
holder.deviceLastSeenText.text = holder.root.context.getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
detailedModeLabels.map {
it.isVisible = true
it.setTypeface(null, if (currentDevice) Typeface.BOLD else Typeface.NORMAL)
}
} else {
holder.summaryLabelText.text = deviceInfo.displayName ?: deviceInfo.deviceId ?: ""
holder.summaryLabelText.isVisible = true
detailedModeLabels.map {
it.isVisible = false
}
}
holder.buttonDelete.isVisible = !currentDevice
holder.buttons.isVisible = buttonsVisible
holder.buttonRename.setOnClickListener { renameClickAction?.invoke() }
holder.buttonDelete.setOnClickListener { deleteClickAction?.invoke() }
}
class Holder : VectorEpoxyHolder() {
val root by bind<ViewGroup>(R.id.itemDeviceRoot)
val summaryLabelText by bind<TextView>(R.id.itemDeviceSimpleSummary)
val displayNameLabelText by bind<TextView>(R.id.itemDeviceDisplayNameLabel)
val displayNameText by bind<TextView>(R.id.itemDeviceDisplayName)
val deviceIdLabelText by bind<TextView>(R.id.itemDeviceIdLabel)
val deviceIdText by bind<TextView>(R.id.itemDeviceId)
val deviceLastSeenLabelText by bind<TextView>(R.id.itemDeviceLastSeenLabel)
val deviceLastSeenText by bind<TextView>(R.id.itemDeviceLastSeen)
val buttons by bind<View>(R.id.itemDeviceButtons)
val buttonDelete by bind<View>(R.id.itemDeviceDelete)
val buttonRename by bind<View>(R.id.itemDeviceRename)
val trustIcon by bind<ImageView>(R.id.itemDeviceTrustLevelIcon)
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 2020 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.riotx.features.settings.devices
import android.os.Bundle
import android.os.Parcelable
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.*
import javax.inject.Inject
@Parcelize
data class DeviceVerificationInfoArgs(
val userId: String,
val deviceId: String
) : Parcelable
class DeviceVerificationInfoBottomSheet : VectorBaseBottomSheetDialogFragment(), DeviceVerificationInfoEpoxyController.Callback {
private val viewModel: DeviceVerificationInfoBottomSheetViewModel by fragmentViewModel(DeviceVerificationInfoBottomSheetViewModel::class)
private val sharedViewModel: DevicesViewModel by parentFragmentViewModel (DevicesViewModel::class)
@Inject lateinit var deviceVerificationInfoViewModelFactory: DeviceVerificationInfoBottomSheetViewModel.Factory
@BindView(R.id.bottomSheetRecyclerView)
lateinit var recyclerView: RecyclerView
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
@Inject lateinit var epoxyController: DeviceVerificationInfoEpoxyController
override fun getLayoutResId() = R.layout.bottom_sheet_generic_list_with_title
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
recyclerView.configureWith(
epoxyController,
showDivider = true,
hasFixedSize = false)
epoxyController.callback = this
bottomSheetTitle.isVisible = false
}
override fun onDestroyView() {
recyclerView.cleanup()
super.onDestroyView()
}
override fun invalidate() = withState(viewModel) {
epoxyController.setData(it)
super.invalidate()
}
companion object {
fun newInstance(userId: String, deviceId: String): DeviceVerificationInfoBottomSheet {
val args = Bundle()
val parcelableArgs = DeviceVerificationInfoArgs(userId, deviceId)
args.putParcelable(MvRx.KEY_ARG, parcelableArgs)
return DeviceVerificationInfoBottomSheet().apply { arguments = args }
}
}
override fun onAction(action: DevicesAction) {
dismiss()
sharedViewModel.handle(action)
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2020 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.riotx.features.settings.devices
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.VectorViewModel
data class DeviceVerificationInfoBottomSheetViewState(
val cryptoDeviceInfo: Async<CryptoDeviceInfo> = Uninitialized
) : MvRxState
class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: DeviceVerificationInfoBottomSheetViewState,
val session: Session
) : VectorViewModel<DeviceVerificationInfoBottomSheetViewState, EmptyAction>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: DeviceVerificationInfoBottomSheetViewState): DeviceVerificationInfoBottomSheetViewModel
}
companion object : MvRxViewModelFactory<DeviceVerificationInfoBottomSheetViewModel, DeviceVerificationInfoBottomSheetViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: DeviceVerificationInfoBottomSheetViewState): DeviceVerificationInfoBottomSheetViewModel? {
val fragment: DeviceVerificationInfoBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.deviceVerificationInfoViewModelFactory.create(state)
}
override fun initialState(viewModelContext: ViewModelContext): DeviceVerificationInfoBottomSheetViewState? {
val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
val args = viewModelContext.args<DeviceVerificationInfoArgs>()
session.getDeviceInfo(args.userId, args.deviceId)?.let {
return DeviceVerificationInfoBottomSheetViewState(cryptoDeviceInfo = Success(it))
}
return super.initialState(viewModelContext)
}
}
override fun handle(action: EmptyAction) {
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2020 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.riotx.features.settings.devices
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.core.epoxy.loadingItem
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.ui.list.GenericItem
import im.vector.riotx.core.ui.list.genericItem
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
class DeviceVerificationInfoEpoxyController @Inject constructor(private val stringProvider: StringProvider,
private val colorProvider: ColorProvider,
private val session: Session,
private val avatarRender: AvatarRenderer)
: TypedEpoxyController<DeviceVerificationInfoBottomSheetViewState>() {
var callback: Callback? = null
override fun buildModels(data: DeviceVerificationInfoBottomSheetViewState?) {
val device = data?.cryptoDeviceInfo?.invoke()
if (device == null) {
loadingItem {
id("loading")
}
} else {
if (device.isVerified) {
genericItem {
id("trust${device.deviceId}")
style(GenericItem.STYLE.BIG_TEXT)
titleIconResourceId(R.drawable.ic_shield_trusted)
title(stringProvider.getString(R.string.encryption_information_verified))
description(stringProvider.getString(R.string.settings_active_sessions_verified_device_desc))
}
} else {
genericItem {
id("trust${device.deviceId}")
titleIconResourceId(R.drawable.ic_shield_warning)
style(GenericItem.STYLE.BIG_TEXT)
title(stringProvider.getString(R.string.encryption_information_not_verified))
description(stringProvider.getString(R.string.settings_active_sessions_unverified_device_desc))
}
}
genericItem {
id("info${device.deviceId}")
title(device.displayName() ?: "")
description("(${device.deviceId})")
}
if (!device.isVerified) {
dividerItem {
id("d1")
}
bottomSheetVerificationActionItem {
id("verify")
title(stringProvider.getString(R.string.verification_verify_device))
titleColor(colorProvider.getColor(R.color.riotx_accent))
iconRes(R.drawable.ic_arrow_right)
iconColor(colorProvider.getColor(R.color.riotx_accent))
listener {
callback?.onAction(DevicesAction.VerifyMyDevice(device.deviceId))
}
}
}
if (device.deviceId != session.sessionParams.credentials.deviceId) {
//Add the delete option
dividerItem {
id("d2")
}
bottomSheetVerificationActionItem {
id("delete")
title(stringProvider.getString(R.string.settings_active_sessions_signout_device))
titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
iconRes(R.drawable.ic_arrow_right)
iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
listener {
callback?.onAction(DevicesAction.Delete(device.deviceId))
}
}
}
dividerItem {
id("d3")
}
bottomSheetVerificationActionItem {
id("rename")
title(stringProvider.getString(R.string.rename))
titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
iconRes(R.drawable.ic_arrow_right)
iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
listener {
callback?.onAction(DevicesAction.PromptRename(device.deviceId))
}
}
}
}
interface Callback {
fun onAction(action: DevicesAction)
}
}

View File

@ -22,17 +22,21 @@ import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.extensions.sortByLastSeen
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.errorWithRetryItem
import im.vector.riotx.core.epoxy.loadingItem
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.ui.list.genericFooterItem
import im.vector.riotx.core.ui.list.genericItemHeader
import im.vector.riotx.features.settings.VectorPreferences
import javax.inject.Inject
class DevicesController @Inject constructor(private val errorFormatter: ErrorFormatter,
private val stringProvider: StringProvider) : EpoxyController() {
private val stringProvider: StringProvider,
private val vectorPreferences: VectorPreferences) : EpoxyController() {
var callback: Callback? = null
private var viewState: DevicesViewState? = null
@ -65,11 +69,11 @@ class DevicesController @Inject constructor(private val errorFormatter: ErrorFor
listener { callback?.retry() }
}
is Success ->
buildDevicesList(devices(), state.myDeviceId, state.currentExpandedDeviceId)
buildDevicesList(devices(), state.cryptoDevices(), state.myDeviceId)
}
}
private fun buildDevicesList(devices: List<DeviceInfo>, myDeviceId: String, currentExpandedDeviceId: String?) {
private fun buildDevicesList(devices: List<DeviceInfo>, cryptoDevices: List<CryptoDeviceInfo>?, myDeviceId: String) {
// Current device
genericItemHeader {
id("current")
@ -83,17 +87,17 @@ class DevicesController @Inject constructor(private val errorFormatter: ErrorFor
.forEachIndexed { idx, deviceInfo ->
deviceItem {
id("myDevice$idx")
detailedMode(vectorPreferences.developerMode())
deviceInfo(deviceInfo)
currentDevice(true)
buttonsVisible(deviceInfo.deviceId == currentExpandedDeviceId)
itemClickAction { callback?.onDeviceClicked(deviceInfo) }
renameClickAction { callback?.onRenameDevice(deviceInfo) }
deleteClickAction { callback?.onDeleteDevice(deviceInfo) }
trusted(true)
}
}
// Other devices
if (devices.size > 1) {
genericItemHeader {
id("others")
text(stringProvider.getString(R.string.devices_other_devices))
@ -109,12 +113,11 @@ class DevicesController @Inject constructor(private val errorFormatter: ErrorFor
val isCurrentDevice = deviceInfo.deviceId == myDeviceId
deviceItem {
id("device$idx")
detailedMode(vectorPreferences.developerMode())
deviceInfo(deviceInfo)
currentDevice(isCurrentDevice)
buttonsVisible(deviceInfo.deviceId == currentExpandedDeviceId)
itemClickAction { callback?.onDeviceClicked(deviceInfo) }
renameClickAction { callback?.onRenameDevice(deviceInfo) }
deleteClickAction { callback?.onDeleteDevice(deviceInfo) }
trusted(cryptoDevices?.firstOrNull { it.deviceId == deviceInfo.deviceId }?.isVerified ?: false)
}
}
}
@ -123,7 +126,5 @@ class DevicesController @Inject constructor(private val errorFormatter: ErrorFor
interface Callback {
fun retry()
fun onDeviceClicked(deviceInfo: DeviceInfo)
fun onRenameDevice(deviceInfo: DeviceInfo)
fun onDeleteDevice(deviceInfo: DeviceInfo)
}
}

View File

@ -18,39 +18,53 @@ package im.vector.riotx.features.settings.devices
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.*
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.platform.VectorViewModelAction
import im.vector.riotx.core.utils.LiveEvent
import timber.log.Timber
data class DevicesViewState(
val myDeviceId: String = "",
val devices: Async<List<DeviceInfo>> = Uninitialized,
val currentExpandedDeviceId: String? = null,
val cryptoDevices: Async<List<CryptoDeviceInfo>> = Uninitialized,
val request: Async<Unit> = Uninitialized
) : MvRxState
sealed class DevicesAction : VectorViewModelAction {
object Retry : DevicesAction()
data class Delete(val deviceInfo: DeviceInfo) : DevicesAction()
data class Delete(val deviceId: String) : DevicesAction()
data class Password(val password: String) : DevicesAction()
data class Rename(val deviceInfo: DeviceInfo, val newName: String) : DevicesAction()
data class ToggleDevice(val deviceInfo: DeviceInfo) : DevicesAction()
data class PromptRename(val deviceId: String, val deviceInfo: DeviceInfo? = null) : DevicesAction()
data class VerifyMyDevice(val deviceId: String, val userId: String? = null, val transactionId: String? = null) : DevicesAction()
}
class DevicesViewModel @AssistedInject constructor(@Assisted initialState: DevicesViewState,
private val session: Session)
: VectorViewModel<DevicesViewState, DevicesAction>(initialState) {
: VectorViewModel<DevicesViewState, DevicesAction>(initialState), SasVerificationService.SasVerificationListener {
@AssistedInject.Factory
interface Factory {
@ -74,8 +88,26 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
val requestPasswordLiveData: LiveData<LiveEvent<Unit>>
get() = _requestPasswordLiveData
// Used to communicate back from model to fragment
private val _requestLiveData = MutableLiveData<LiveEvent<Async<DevicesAction>>>()
val fragmentActionLiveData: LiveData<LiveEvent<Async<DevicesAction>>>
get() = _requestLiveData
init {
refreshDevicesList()
session.getSasVerificationService().addListener(this)
}
override fun onCleared() {
session.getSasVerificationService().removeListener(this)
super.onCleared()
}
override fun transactionCreated(tx: SasVerificationTransaction) {}
override fun transactionUpdated(tx: SasVerificationTransaction) {
if(tx.state == SasVerificationTxState.Verified) {
refreshDevicesList()
}
}
/**
@ -109,6 +141,25 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
}
}
})
// Put cached state
setState {
copy(
myDeviceId = session.sessionParams.credentials.deviceId ?: "",
cryptoDevices = Success(session.getUserDevices(session.myUserId))
)
}
// then force download
session.downloadKeys(listOf(session.myUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
setState {
copy(
cryptoDevices = Success(session.getUserDevices(session.myUserId))
)
}
}
})
} else {
// Should not happen
}
@ -116,21 +167,34 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
override fun handle(action: DevicesAction) {
return when (action) {
is DevicesAction.Retry -> refreshDevicesList()
is DevicesAction.Delete -> handleDelete(action)
is DevicesAction.Password -> handlePassword(action)
is DevicesAction.Rename -> handleRename(action)
is DevicesAction.ToggleDevice -> handleToggleDevice(action)
is DevicesAction.Retry -> refreshDevicesList()
is DevicesAction.Delete -> handleDelete(action)
is DevicesAction.Password -> handlePassword(action)
is DevicesAction.Rename -> handleRename(action)
is DevicesAction.PromptRename -> handlePromptRename(action)
is DevicesAction.VerifyMyDevice -> handleVerify(action)
}
}
private fun handleToggleDevice(action: DevicesAction.ToggleDevice) {
withState {
setState {
copy(
currentExpandedDeviceId = if (it.currentExpandedDeviceId == action.deviceInfo.deviceId) null else action.deviceInfo.deviceId
)
}
private fun handleVerify(action: DevicesAction.VerifyMyDevice) {
// TODO Implement request in to DEVICE!!!
val txID = session.getSasVerificationService().beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId)
if (txID != null) {
_requestLiveData.postValue(LiveEvent(Success(
action.copy(
userId = session.myUserId,
transactionId = txID
)
)))
}
}
private fun handlePromptRename(action: DevicesAction.PromptRename) = withState { state ->
val info = state.devices.invoke()?.firstOrNull { it.deviceId == action.deviceId }
if (info == null) {
_requestLiveData.postValue(LiveEvent(Uninitialized))
} else {
_requestLiveData.postValue(LiveEvent(Success(action.copy(deviceInfo = info))))
}
}
@ -162,11 +226,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
* Try to delete a device.
*/
private fun handleDelete(action: DevicesAction.Delete) {
val deviceId = action.deviceInfo.deviceId
if (deviceId == null) {
Timber.e("## handleDelete(): sanity check failure")
return
}
val deviceId = action.deviceId
setState {
copy(

View File

@ -25,6 +25,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
@ -35,6 +36,7 @@ import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
import javax.inject.Inject
@ -69,6 +71,29 @@ class VectorSettingsDevicesFragment @Inject constructor(
devicesViewModel.requestPasswordLiveData.observeEvent(this) {
maybeShowDeleteDeviceWithPasswordDialog()
}
devicesViewModel.fragmentActionLiveData.observeEvent(this) { async ->
when (async) {
is Success -> {
when (val action = async.invoke()) {
is DevicesAction.PromptRename -> {
action.deviceInfo?.let { deviceInfo ->
displayDeviceRenameDialog(deviceInfo)
}
}
is DevicesAction.VerifyMyDevice -> {
if (context is VectorBaseActivity) {
VerificationBottomSheet.withArgs(
roomId = null,
otherUserId = action.userId!!,
transactionId = action.transactionId!!
).show(childFragmentManager, "REQPOP")
}
}
}
}
}
}
}
override fun onDestroyView() {
@ -80,7 +105,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
override fun onResume() {
super.onResume()
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_devices_list)
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_active_sessions_manage)
}
private fun displayErrorDialog(throwable: Throwable) {
@ -92,16 +117,19 @@ class VectorSettingsDevicesFragment @Inject constructor(
}
override fun onDeviceClicked(deviceInfo: DeviceInfo) {
devicesViewModel.handle(DevicesAction.ToggleDevice(deviceInfo))
DeviceVerificationInfoBottomSheet.newInstance(deviceInfo.user_id ?: "", deviceInfo.deviceId ?: "").show(
childFragmentManager,
"VERIF_INFO"
)
}
override fun onDeleteDevice(deviceInfo: DeviceInfo) {
devicesViewModel.handle(DevicesAction.Delete(deviceInfo))
}
override fun onRenameDevice(deviceInfo: DeviceInfo) {
displayDeviceRenameDialog(deviceInfo)
}
// override fun onDeleteDevice(deviceInfo: DeviceInfo) {
// devicesViewModel.handle(DevicesAction.Delete(deviceInfo))
// }
//
// override fun onRenameDevice(deviceInfo: DeviceInfo) {
// displayDeviceRenameDialog(deviceInfo)
// }
override fun retry() {
devicesViewModel.handle(DevicesAction.Retry)

View File

@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:background="?vctr_list_header_background_color"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView

View File

@ -6,93 +6,90 @@
android:layout_height="wrap_content"
android:background="?riotx_background"
android:foreground="?attr/selectableItemBackground"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/itemDeviceDisplayNameLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/devices_details_name_title"
android:textColor="?riotx_text_secondary"
android:textSize="12sp" />
<TextView
android:id="@+id/itemDeviceDisplayName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
tools:text="Android phone" />
<TextView
android:id="@+id/itemDeviceIdLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/devices_details_id_title"
android:textColor="?riotx_text_secondary"
android:textSize="12sp" />
<TextView
android:id="@+id/itemDeviceId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
tools:text="XUIDERFZAA" />
<TextView
android:id="@+id/itemDeviceLastSeenLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/devices_details_last_seen_title"
android:textColor="?riotx_text_secondary"
android:textSize="12sp" />
<TextView
android:id="@+id/itemDeviceLastSeen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
tools:text="x.x.x.x @ 01/01 00:00:00" />
android:orientation="horizontal"
android:padding="8dp">
<LinearLayout
android:id="@+id/itemDeviceButtons"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="4dp"
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
android:layout_weight="1"
android:orientation="vertical">
<com.google.android.material.button.MaterialButton
android:id="@+id/itemDeviceRename"
style="@style/VectorButtonStyleText"
android:layout_width="wrap_content"
<!-- In compact mode only this is shown-->
<TextView
android:id="@+id/itemDeviceSimpleSummary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/rename" />
android:gravity="center_vertical"
android:minHeight="40dp"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
tools:text="Riot X" />
<com.google.android.material.button.MaterialButton
android:id="@+id/itemDeviceDelete"
style="@style/VectorButtonStyleText"
android:layout_width="wrap_content"
<!---
The following detailed informations are displayed in developper mode
-->
<TextView
android:id="@+id/itemDeviceDisplayNameLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/delete"
android:textColor="@color/riotx_notice"
android:visibility="gone"
tools:visibility="visible" />
android:layout_marginTop="6dp"
android:text="@string/devices_details_name_title"
android:textColor="?riotx_text_secondary"
android:textSize="12sp" />
<TextView
android:id="@+id/itemDeviceDisplayName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
tools:text="Android phone" />
<TextView
android:id="@+id/itemDeviceIdLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/devices_details_id_title"
android:textColor="?riotx_text_secondary"
android:textSize="12sp" />
<TextView
android:id="@+id/itemDeviceId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
tools:text="XUIDERFZAA" />
<TextView
android:id="@+id/itemDeviceLastSeenLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/devices_details_last_seen_title"
android:textColor="?riotx_text_secondary"
android:textSize="12sp" />
<TextView
android:id="@+id/itemDeviceLastSeen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
tools:text="x.x.x.x @ 01/01 00:00:00" />
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/itemDeviceTrustLevelIcon"
android:layout_width="50dp"
android:layout_height="match_parent"
android:padding="8dp"
tools:src="@drawable/ic_shield_trusted" />
</LinearLayout>

View File

@ -8,24 +8,36 @@
android:background="?android:attr/colorBackground"
android:minHeight="50dp">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:id="@+id/item_generic_title_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/item_generic_title_text"
app:layout_constraintBottom_toBottomOf="@id/item_generic_title_text"
android:layout_marginStart="16dp"
android:scaleType="centerInside"
tools:src="@drawable/ic_shield_trusted"
tools:visibility="visible"
android:visibility="gone"/>
<TextView
android:id="@+id/item_generic_title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="8dp"
android:layout_marginBottom="4dp"
android:textColor="?riotx_text_primary"
android:textStyle="bold"
android:visibility="gone"
app:layout_constrainedWidth="true"
app:layout_goneMarginStart="16dp"
app:layout_constraintBottom_toTopOf="@+id/item_generic_description_text"
app:layout_constraintEnd_toStartOf="@+id/item_generic_barrier"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toEndOf="@id/item_generic_title_image"
app:layout_constraintTop_toTopOf="parent"
tools:text="Item Title"
tools:textSize="14sp"

View File

@ -7,7 +7,7 @@
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="72dp"
android:minHeight="64dp"
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"

View File

@ -32,6 +32,9 @@
<string name="verification_sent">Verification Sent</string>
<string name="verification_request">Verification Request</string>
<string name="verification_verify_device">Verify this device</string>
<!-- Sender name of a message when it is send by you, e.g. You: Hello!-->
<string name="you">You</string>
@ -102,4 +105,18 @@
<string name="encryption_information_dg_xsigning_not_trusted">Cross-Signing is enabled\nKeys are not trusted</string>
<string name="encryption_information_dg_xsigning_disabled">Cross-Signing is not enabled</string>
<string name="settings_active_sessions_list">Active Sessions</string>
<string name="settings_active_sessions_show_all">Show All Sessions</string>
<string name="settings_active_sessions_manage">Manage Sessions</string>
<string name="settings_active_sessions_signout_device">Sign out this device</string>
<string name="settings_active_sessions_verified_device_desc">This session is trusted for secure messaging because you verified it:</string>
<string name="settings_active_sessions_unverified_device_desc">Verify this session to mark it as trusted &amp; grant it access to encrypted messages. If you didnt sign in to this device your account may be compromised:</string>
<plurals name="settings_active_sessions_count">
<item quantity="one">%d active session</item>
<item quantity="other">%d active sessions</item>
</plurals>
</resources>

View File

@ -6,42 +6,46 @@
<!-- ************ Cryptography section ************ -->
<im.vector.riotx.core.preference.VectorPreferenceCategory
android:key="SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY"
tools:isPreferenceVisible="true"
app:isPreferenceVisible="false"
android:title="@string/settings_cryptography">
<im.vector.riotx.core.preference.VectorPreference
android:key="SETTINGS_ENCRYPTION_CROSS_SIGNING_PREFERENCE_KEY"
tools:icon="@drawable/ic_shield_trusted"
android:persistent="false"
android:title="@string/encryption_information_cross_signing_state" />
android:title="@string/encryption_information_cross_signing_state"
tools:summary="@string/encryption_information_dg_xsigning_complete"
/>
<im.vector.riotx.core.preference.VectorPreference
android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY"
android:title="@string/encryption_information_device_name" />
<!-- <im.vector.riotx.core.preference.VectorPreference-->
<!-- android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY"-->
<!-- android:title="@string/encryption_information_device_name" />-->
<im.vector.riotx.core.preference.VectorPreference
android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY"
android:title="@string/encryption_information_device_id" />
<!-- <im.vector.riotx.core.preference.VectorPreference-->
<!-- android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY"-->
<!-- android:title="@string/encryption_information_device_id" />-->
<im.vector.riotx.core.preference.VectorPreference
android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY"
android:title="@string/encryption_information_device_key" />
<!-- <im.vector.riotx.core.preference.VectorPreference-->
<!-- android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY"-->
<!-- android:title="@string/encryption_information_device_key" />-->
<im.vector.riotx.core.preference.VectorSwitchPreference
android:key="SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY"
android:summary="@string/encryption_never_send_to_unverified_devices_summary"
android:title="@string/encryption_never_send_to_unverified_devices_title"
app:isPreferenceVisible="@bool/false_not_implemented" />
android:enabled="false" />
</im.vector.riotx.core.preference.VectorPreferenceCategory>
<!-- devices list entry point -->
<im.vector.riotx.core.preference.VectorPreferenceCategory
android:key="SETTINGS_DEVICES_LIST_PREFERENCE_KEY"
android:title="@string/settings_devices_list">
android:title="@string/settings_active_sessions_list">
<im.vector.riotx.core.preference.VectorPreference
android:key="SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY"
android:title="@string/settings_show_devices_list"
android:title="@string/settings_active_sessions_show_all"
app:fragment="im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment" />
</im.vector.riotx.core.preference.VectorPreferenceCategory>