Device list: remove the detail dialog: handle the actions directly in the list
This commit is contained in:
parent
6b2703f6ce
commit
8dff196716
|
@ -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<DeviceItem.Holder>() {
|
|||
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<View>(R.id.itemDeviceRoot)
|
||||
val root by bind<ViewGroup>(R.id.itemDeviceRoot)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import timber.log.Timber
|
|||
data class DevicesViewState(
|
||||
val myDeviceId: String = "",
|
||||
val devices: Async<List<DeviceInfo>> = Uninitialized,
|
||||
val currentExpandedDeviceId: String? = null,
|
||||
val request: Async<Unit> = 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<TextView>(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() {
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/device_container_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="?dialogPreferredPadding"
|
||||
android:paddingLeft="?dialogPreferredPadding"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="?dialogPreferredPadding"
|
||||
android:paddingRight="?dialogPreferredPadding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_id_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/devices_details_id_title"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_id"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
tools:text="a device id" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_name_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/devices_details_name_title"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
tools:text="a device name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_last_seen_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/devices_details_last_seen_title"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_last_seen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
tools:text="x.x.x.x @ 01/01 00:00:00" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/itemDeviceRoot"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -13,32 +12,87 @@
|
|||
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"
|
||||
app:layout_constraintBottom_toTopOf="@+id/itemUserName"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/itemUserAvatar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
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:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/itemUserAvatar"
|
||||
app:layout_constraintTop_toBottomOf="@+id/itemUserId"
|
||||
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
|
||||
android:id="@+id/itemDeviceButtons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="4dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/itemDeviceRename"
|
||||
style="@style/VectorButtonStyleText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/rename" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/itemDeviceDelete"
|
||||
style="@style/VectorButtonStyleText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="@string/delete"
|
||||
android:textColor="@color/riotx_notice"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
Loading…
Reference in New Issue