diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 53b2442625..ecd666948d 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -184,7 +184,17 @@ internal class DefaultCryptoService @Inject constructor( override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback) { setDeviceNameTask .configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) { - this.callback = callback + this.callback = object : MatrixCallback { + override fun onSuccess(data: Unit) { + //bg refresh of crypto device + downloadKeys(listOf(credentials.userId), true, object : MatrixCallback> {}) + callback.onSuccess(data) + } + + override fun onFailure(failure: Throwable) { + callback.onFailure(failure) + } + } } .executeBy(taskExecutor) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 878280295b..325f3b7563 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt index 4503143052..85e761e0b9 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt @@ -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, diff --git a/vector/src/main/java/im/vector/riotx/core/ui/list/GenericItem.kt b/vector/src/main/java/im/vector/riotx/core/ui/list/GenericItem.kt index c936ba8164..fc622f4dcb 100644 --- a/vector/src/main/java/im/vector/riotx/core/ui/list/GenericItem.kt +++ b/vector/src/main/java/im/vector/riotx/core/ui/list/GenericItem.kt @@ -60,6 +60,10 @@ abstract class GenericItem : VectorEpoxyModel() { @DrawableRes var endIconResourceId: Int = -1 + @EpoxyAttribute + @DrawableRes + var titleIconResourceId: Int = -1 + @EpoxyAttribute var hasIndeterminateProcess = false @@ -72,6 +76,13 @@ abstract class GenericItem : VectorEpoxyModel() { 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() { class Holder : VectorEpoxyHolder() { val root by bind(R.id.item_generic_root) - + val titleIcon by bind(R.id.item_generic_title_image) val titleText by bind(R.id.item_generic_title_text) val descriptionText by bind(R.id.item_generic_description_text) val accessoryImage by bind(R.id.item_generic_accessory_image) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index a79cf997a7..816b5b3fee 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -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" diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 3c984bf769..833e368b35 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -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(VectorPreferences.SETTINGS_ENCRYPTION_CROSS_SIGNING_PREFERENCE_KEY)!! } - private val cryptoInfoDeviceNamePreference by lazy { - findPreference(VectorPreferences.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY)!! - } - - private val cryptoInfoDeviceIdPreference by lazy { - findPreference(VectorPreferences.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY)!! - } - private val manageBackupPref by lazy { findPreference(VectorPreferences.SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY)!! } @@ -100,9 +92,10 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( findPreference(VectorPreferences.SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY)!! } - private val cryptoInfoTextPreference by lazy { - findPreference(VectorPreferences.SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY)!! + private val showDeviceListPref by lazy { + findPreference(VectorPreferences.SETTINGS_SHOW_DEVICES_LIST_PREFERENCE_KEY)!! } + // encrypt to unverified devices private val sendToUnverifiedDevicesPref by lazy { findPreference(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) { + 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 { - override fun onFailure(failure: Throwable) { - // Ignore for this time?... - } + session.getDevicesList(object : MatrixCallback { + 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()) + } + }) } // ============================================================================================================== diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceItem.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceItem.kt index b6c84ade9a..17728b5c1a 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceItem.kt @@ -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() { @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(R.id.itemDeviceRoot) + val summaryLabelText by bind(R.id.itemDeviceSimpleSummary) val displayNameLabelText by bind(R.id.itemDeviceDisplayNameLabel) val displayNameText by bind(R.id.itemDeviceDisplayName) val deviceIdLabelText by bind(R.id.itemDeviceIdLabel) val deviceIdText by bind(R.id.itemDeviceId) val deviceLastSeenLabelText by bind(R.id.itemDeviceLastSeenLabel) val deviceLastSeenText by bind(R.id.itemDeviceLastSeen) - val buttons by bind(R.id.itemDeviceButtons) - val buttonDelete by bind(R.id.itemDeviceDelete) - val buttonRename by bind(R.id.itemDeviceRename) + + val trustIcon by bind(R.id.itemDeviceTrustLevelIcon) } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheet.kt new file mode 100644 index 0000000000..d819a7af24 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheet.kt @@ -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) + } + +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt new file mode 100644 index 0000000000..9266b471e7 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt @@ -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 = Uninitialized +) : MvRxState + +class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: DeviceVerificationInfoBottomSheetViewState, + val session: Session +) : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: DeviceVerificationInfoBottomSheetViewState): DeviceVerificationInfoBottomSheetViewModel + } + + companion object : MvRxViewModelFactory { + + @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() + session.getDeviceInfo(args.userId, args.deviceId)?.let { + return DeviceVerificationInfoBottomSheetViewState(cryptoDeviceInfo = Success(it)) + } + return super.initialState(viewModelContext) + } + } + + override fun handle(action: EmptyAction) { + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoEpoxyController.kt new file mode 100644 index 0000000000..a323dd4358 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoEpoxyController.kt @@ -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() { + + 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) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesController.kt index 18c0965f86..e3c8e78034 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesController.kt @@ -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, myDeviceId: String, currentExpandedDeviceId: String?) { + private fun buildDevicesList(devices: List, cryptoDevices: List?, 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) } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt index b2b015a3f0..c0ee70f73d 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt @@ -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> = Uninitialized, - val currentExpandedDeviceId: String? = null, + val cryptoDevices: Async> = Uninitialized, val request: Async = 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(initialState) { + : VectorViewModel(initialState), SasVerificationService.SasVerificationListener { @AssistedInject.Factory interface Factory { @@ -74,8 +88,26 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic val requestPasswordLiveData: LiveData> get() = _requestPasswordLiveData + // Used to communicate back from model to fragment + private val _requestLiveData = MutableLiveData>>() + val fragmentActionLiveData: LiveData>> + 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> { + override fun onSuccess(data: MXUsersDevicesMap) { + 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( diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt index 465b3ba0fb..dd7dca38da 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -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) diff --git a/vector/src/main/res/layout/fragment_generic_recycler.xml b/vector/src/main/res/layout/fragment_generic_recycler.xml index b06e82f9ce..1c2dcc1c3a 100644 --- a/vector/src/main/res/layout/fragment_generic_recycler.xml +++ b/vector/src/main/res/layout/fragment_generic_recycler.xml @@ -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"> - - - - - - - - - - - - + android:orientation="horizontal" + android:padding="8dp"> + android:layout_weight="1" + android:orientation="vertical"> - + + android:gravity="center_vertical" + android:minHeight="40dp" + android:textColor="?riotx_text_primary" + android:textSize="16sp" + tools:text="Riot X" /> - + + android:layout_marginTop="6dp" + android:text="@string/devices_details_name_title" + android:textColor="?riotx_text_secondary" + android:textSize="12sp" /> + + + + + + + + + + - \ No newline at end of file + + + + diff --git a/vector/src/main/res/layout/item_generic_list.xml b/vector/src/main/res/layout/item_generic_list.xml index 8fdea1c17d..075cbe6da5 100644 --- a/vector/src/main/res/layout/item_generic_list.xml +++ b/vector/src/main/res/layout/item_generic_list.xml @@ -8,24 +8,36 @@ android:background="?android:attr/colorBackground" android:minHeight="50dp"> + + Verification Sent Verification Request + + Verify this device + You @@ -102,4 +105,18 @@ Cross-Signing is enabled\nKeys are not trusted Cross-Signing is not enabled + + Active Sessions + Show All Sessions + Manage Sessions + Sign out this device + + This session is trusted for secure messaging because you verified it: + 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: + + + %d active session + %d active sessions + + diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml index 03d39b839a..913765bc3a 100644 --- a/vector/src/main/res/xml/vector_settings_security_privacy.xml +++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml @@ -6,42 +6,46 @@ + android:title="@string/encryption_information_cross_signing_state" + tools:summary="@string/encryption_information_dg_xsigning_complete" + /> - + + + - + + + - + + + + android:enabled="false" /> + android:title="@string/settings_active_sessions_list">