Show basic details info

This commit is contained in:
Maxime NATUREL 2022-09-08 16:46:40 +02:00
parent 94d1649ec9
commit bf88c16c9d
7 changed files with 224 additions and 4 deletions

View file

@ -3267,6 +3267,12 @@
<string name="device_manager_session_last_activity">Last activity %1$s</string> <string name="device_manager_session_last_activity">Last activity %1$s</string>
<string name="device_manager_session_details_title">Session details</string> <string name="device_manager_session_details_title">Session details</string>
<string name="device_manager_session_details_description">Application, device, and activity information.</string> <string name="device_manager_session_details_description">Application, device, and activity information.</string>
<string name="device_manager_session_details_section_session_title">Session</string>
<string name="device_manager_session_details_session_name">Session name</string>
<string name="device_manager_session_details_session_id">Session ID</string>
<string name="device_manager_session_details_session_last_activity">Last activity</string>
<string name="device_manager_session_details_section_device_title">Device</string>
<string name="device_manager_session_details_device_ip_address">IP address</string>
<!-- Note to translators: %s will be replaces with selected space name --> <!-- Note to translators: %s will be replaces with selected space name -->
<string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string> <string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string>

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2022 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.app.features.settings.devices.v2.details
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import javax.inject.Inject
// TODO add unit tests
class CheckIfSectionDeviceIsVisibleUseCase @Inject constructor() {
fun execute(deviceInfo: DeviceInfo): Boolean {
return deviceInfo.lastSeenIp?.isNotEmpty().orFalse()
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 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.app.features.settings.devices.v2.details
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import javax.inject.Inject
// TODO add unit tests
class CheckIfSectionSessionIsVisibleUseCase @Inject constructor() {
fun execute(deviceInfo: DeviceInfo): Boolean {
return deviceInfo.displayName?.isNotEmpty().orFalse() ||
deviceInfo.deviceId?.isNotEmpty().orFalse() ||
(deviceInfo.lastSeenTs ?: 0) > 0
}
}

View file

@ -25,7 +25,7 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
@EpoxyModelClass @EpoxyModelClass
abstract class SessionDetailsContentItem : VectorEpoxyModel<SessionDetailsContentItem.Holder>(R.layout.item_session_details_header) { abstract class SessionDetailsContentItem : VectorEpoxyModel<SessionDetailsContentItem.Holder>(R.layout.item_session_details_content) {
@EpoxyAttribute @EpoxyAttribute
var title: String? = null var title: String? = null

View file

@ -0,0 +1,109 @@
/*
* Copyright (c) 2022 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.app.features.settings.devices.v2.details
import androidx.annotation.StringRes
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import javax.inject.Inject
class SessionDetailsController @Inject constructor(
private val checkIfSectionSessionIsVisibleUseCase: CheckIfSectionSessionIsVisibleUseCase,
private val checkIfSectionDeviceIsVisibleUseCase: CheckIfSectionDeviceIsVisibleUseCase,
private val stringProvider: StringProvider,
private val dateFormatter: VectorDateFormatter,
) : TypedEpoxyController<DeviceInfo>() {
var callback: Callback? = null
interface Callback {
fun onItemLongClicked(content: String)
}
override fun buildModels(data: DeviceInfo?) {
data?.let { info ->
if (hasSectionSession(data)) {
buildSectionSession(info)
}
if (hasSectionDevice(data)) {
// TODO add a marginTop of 48dp if the session section is visible
buildSectionDevice(info)
}
}
}
private fun buildHeaderItem(@StringRes titleResId: Int) {
val host = this
sessionDetailsHeaderItem {
id(titleResId)
title(host.stringProvider.getString(titleResId))
}
}
private fun buildContentItem(@StringRes titleResId: Int, value: String) {
val host = this
sessionDetailsContentItem {
id(titleResId)
title(host.stringProvider.getString(titleResId))
description(value)
}
}
private fun hasSectionSession(data: DeviceInfo): Boolean {
return checkIfSectionSessionIsVisibleUseCase.execute(data)
}
private fun buildSectionSession(data: DeviceInfo) {
val sessionName = data.displayName
val sessionId = data.deviceId
val sessionLastSeenTs = data.lastSeenTs
buildHeaderItem(R.string.device_manager_session_details_section_session_title)
// TODO hide divider on the last visible item
sessionName?.let {
buildContentItem(R.string.device_manager_session_details_session_name, it)
}
sessionId?.let {
buildContentItem(R.string.device_manager_session_details_session_id, it)
}
sessionLastSeenTs?.let {
val formattedDate = dateFormatter.format(it, DateFormatKind.MESSAGE_DETAIL)
buildContentItem(R.string.device_manager_session_details_session_last_activity, formattedDate)
}
}
private fun hasSectionDevice(data: DeviceInfo): Boolean {
return checkIfSectionDeviceIsVisibleUseCase.execute(data)
}
private fun buildSectionDevice(data: DeviceInfo) {
val lastSeenIp = data.lastSeenIp
buildHeaderItem(R.string.device_manager_session_details_section_device_title)
// TODO hide divider on the last visible item
lastSeenIp?.let {
buildContentItem(R.string.device_manager_session_details_device_ip_address, it)
}
}
}

View file

@ -21,11 +21,19 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSessionDetailsBinding import im.vector.app.databinding.FragmentSessionDetailsBinding
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import javax.inject.Inject
/** /**
* Display the details info about a Session. * Display the details info about a Session.
@ -34,6 +42,8 @@ import im.vector.app.databinding.FragmentSessionDetailsBinding
class SessionDetailsFragment : class SessionDetailsFragment :
VectorBaseFragment<FragmentSessionDetailsBinding>() { VectorBaseFragment<FragmentSessionDetailsBinding>() {
@Inject lateinit var sessionDetailsController: SessionDetailsController
private val viewModel: SessionDetailsViewModel by fragmentViewModel() private val viewModel: SessionDetailsViewModel by fragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSessionDetailsBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSessionDetailsBinding {
@ -43,6 +53,7 @@ class SessionDetailsFragment :
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initToolbar() initToolbar()
initSessionDetails()
} }
private fun initToolbar() { private fun initToolbar() {
@ -51,14 +62,33 @@ class SessionDetailsFragment :
?.setTitle(R.string.device_manager_session_details_title) ?.setTitle(R.string.device_manager_session_details_title)
} }
/*override fun invalidate() = withState(viewModel) { state -> private fun initSessionDetails() {
views.sessionDetails.configureWith(sessionDetailsController)
}
override fun onDestroyView() {
cleanUpSessionDetails()
super.onDestroyView()
}
private fun cleanUpSessionDetails() {
views.sessionDetails.cleanup()
}
override fun invalidate() = withState(viewModel) { state ->
if (state.deviceInfo is Success) { if (state.deviceInfo is Success) {
renderSessionDetails(state.deviceInfo.invoke()) renderSessionDetails(state.deviceInfo.invoke())
} else { } else {
hideSessionInfo() hideSessionDetails()
} }
} }
private fun renderSessionDetails(deviceInfo: DeviceInfo) { private fun renderSessionDetails(deviceInfo: DeviceInfo) {
}*/ views.sessionDetails.isVisible = true
sessionDetailsController.setData(deviceInfo)
}
private fun hideSessionDetails() {
views.sessionDetails.isGone = true
}
} }

View file

@ -1,6 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sessionDetails"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:listitem="@layout/item_session_details_content" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>