From 8dff196716a32683eebe32fe797d3b6146b7f3c5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 2 Jan 2020 15:42:42 +0100 Subject: [PATCH] Device list: remove the detail dialog: handle the actions directly in the list --- .../features/settings/devices/DeviceItem.kt | 61 +++++++++++++-- .../settings/devices/DevicesController.kt | 13 ++-- .../settings/devices/DevicesViewModel.kt | 21 ++++- .../devices/VectorSettingsDevicesFragment.kt | 64 +++------------ .../main/res/layout/dialog_device_details.xml | 66 ---------------- vector/src/main/res/layout/item_device.xml | 78 ++++++++++++++++--- 6 files changed, 153 insertions(+), 150 deletions(-) delete mode 100644 vector/src/main/res/layout/dialog_device_details.xml 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 201d4c88dd..b6c84ade9a 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 @@ -18,13 +18,18 @@ package im.vector.riotx.features.settings.devices import android.graphics.Typeface import android.view.View +import android.view.ViewGroup import android.widget.TextView +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.riotx.R 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.* /** * A list item for Device. @@ -36,29 +41,69 @@ abstract class DeviceItem : VectorEpoxyModel() { lateinit var deviceInfo: DeviceInfo @EpoxyAttribute - var bold = false + var currentDevice = false + + @EpoxyAttribute + var buttonsVisible = false @EpoxyAttribute var itemClickAction: (() -> Unit)? = null + @EpoxyAttribute + var renameClickAction: (() -> Unit)? = null + + @EpoxyAttribute + var deleteClickAction: (() -> Unit)? = null + override fun bind(holder: Holder) { holder.root.setOnClickListener { itemClickAction?.invoke() } holder.displayNameText.text = deviceInfo.displayName ?: "" holder.deviceIdText.text = deviceInfo.deviceId ?: "" - if (bold) { - holder.displayNameText.setTypeface(null, Typeface.BOLD) - holder.deviceIdText.setTypeface(null, Typeface.BOLD) - } else { - holder.displayNameText.setTypeface(null, Typeface.NORMAL) - holder.deviceIdText.setTypeface(null, Typeface.NORMAL) + 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( + holder.displayNameLabelText, + holder.displayNameText, + holder.deviceIdLabelText, + holder.deviceIdText, + holder.deviceLastSeenLabelText, + holder.deviceLastSeenText + ).map { + it.setTypeface(null, if (currentDevice) Typeface.BOLD else Typeface.NORMAL) } + + 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 root by bind(R.id.itemDeviceRoot) + 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) } } 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 6fe25335df..db61db429b 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 @@ -72,8 +72,11 @@ class DevicesController @Inject constructor(private val errorFormatter: ErrorFor deviceItem { id("device$idx") deviceInfo(deviceInfo) - bold(isCurrentDevice) - itemClickAction { callback?.onDeviceClicked(deviceInfo, isCurrentDevice) } + currentDevice(isCurrentDevice) + buttonsVisible(deviceInfo.deviceId == state.currentExpandedDeviceId) + itemClickAction { callback?.onDeviceClicked(deviceInfo) } + renameClickAction { callback?.onRenameDevice(deviceInfo) } + deleteClickAction { callback?.onDeleteDevice(deviceInfo) } } } } @@ -81,8 +84,8 @@ class DevicesController @Inject constructor(private val errorFormatter: ErrorFor interface Callback { fun retry() - fun onDeviceClicked(deviceInfo: DeviceInfo, isCurrentDevice: Boolean) + 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 c06912cdeb..1fc56e76fd 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 @@ -36,6 +36,7 @@ import timber.log.Timber data class DevicesViewState( val myDeviceId: String = "", val devices: Async> = Uninitialized, + val currentExpandedDeviceId: String? = null, val request: Async = Uninitialized ) : MvRxState @@ -44,6 +45,7 @@ sealed class DevicesAction : VectorViewModelAction { data class Delete(val deviceInfo: DeviceInfo) : 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() } class DevicesViewModel @AssistedInject constructor(@Assisted initialState: DevicesViewState, @@ -114,10 +116,21 @@ 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.Retry -> refreshDevicesList() + is DevicesAction.Delete -> handleDelete(action) + is DevicesAction.Password -> handlePassword(action) + is DevicesAction.Rename -> handleRename(action) + is DevicesAction.ToggleDevice -> handleToggleDevice(action) + } + } + + private fun handleToggleDevice(action: DevicesAction.ToggleDevice) { + withState { + setState { + copy( + currentExpandedDeviceId = if (it.currentExpandedDeviceId == action.deviceInfo.deviceId) null else action.deviceInfo.deviceId + ) + } } } 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 96a6d11e06..4168e3eeb7 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 @@ -21,7 +21,6 @@ import android.os.Bundle import android.view.KeyEvent import android.view.View import android.widget.EditText -import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import com.airbnb.mvrx.Async @@ -36,12 +35,8 @@ 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.settings.VectorSettingsSecurityPrivacyFragment import kotlinx.android.synthetic.main.fragment_generic_recycler.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.* import javax.inject.Inject /** @@ -97,63 +92,22 @@ class VectorSettingsDevicesFragment @Inject constructor( } /** - * Display a dialog containing the device ID, the device name and the "last seen" information.<> + * Display a dialog containing the device ID, the device name and the "last seen" information. * This dialog allow to delete the corresponding device (see [.displayDeviceDeletionDialog]) * * @param deviceInfo the device information * @param isCurrentDevice true if this is the current device */ - override fun onDeviceClicked(deviceInfo: DeviceInfo, isCurrentDevice: Boolean) { - val builder = AlertDialog.Builder(requireActivity()) - val inflater = requireActivity().layoutInflater - val layout = inflater.inflate(R.layout.dialog_device_details, null) - var textView = layout.findViewById(R.id.device_id) + override fun onDeviceClicked(deviceInfo: DeviceInfo) { + devicesViewModel.handle(DevicesAction.ToggleDevice(deviceInfo)) + } - textView.text = deviceInfo.deviceId + override fun onDeleteDevice(deviceInfo: DeviceInfo) { + devicesViewModel.handle(DevicesAction.Delete(deviceInfo)) + } - // device name - textView = layout.findViewById(R.id.device_name) - val displayName = if (deviceInfo.displayName.isNullOrEmpty()) VectorSettingsSecurityPrivacyFragment.LABEL_UNAVAILABLE_DATA else deviceInfo.displayName - textView.text = displayName - - // last seen info - textView = layout.findViewById(R.id.device_last_seen) - - 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 - } ?: "-" - - val lastSeenInfo = getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime) - textView.text = lastSeenInfo - - // title & icon - builder.setTitle(R.string.devices_details_dialog_title) - .setIcon(android.R.drawable.ic_dialog_info) - .setView(layout) - .setPositiveButton(R.string.rename) { _, _ -> displayDeviceRenameDialog(deviceInfo) } - - // disable the deletion for our own device - if (!isCurrentDevice) { - builder.setNegativeButton(R.string.delete) { _, _ -> devicesViewModel.handle(DevicesAction.Delete(deviceInfo)) } - } - - builder.setNeutralButton(R.string.cancel, null) - .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> - if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { - dialog.cancel() - return@OnKeyListener true - } - false - }) - .show() + override fun onRenameDevice(deviceInfo: DeviceInfo) { + displayDeviceRenameDialog(deviceInfo) } override fun retry() { diff --git a/vector/src/main/res/layout/dialog_device_details.xml b/vector/src/main/res/layout/dialog_device_details.xml deleted file mode 100644 index b3b5c5aff7..0000000000 --- a/vector/src/main/res/layout/dialog_device_details.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/item_device.xml b/vector/src/main/res/layout/item_device.xml index 59b7ba92c0..bebaf156d9 100644 --- a/vector/src/main/res/layout/item_device.xml +++ b/vector/src/main/res/layout/item_device.xml @@ -1,6 +1,5 @@ + + + + + + + + + + + + + + + + \ No newline at end of file