mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Update My device list + action to verify
This commit is contained in:
parent
a0aa1f34d3
commit
1276d1f39d
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ==============================================================================================================
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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 & grant it access to encrypted messages. If you didn’t 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>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user